Skip to content

Commit

Permalink
Update README and CHANGELOG
Browse files Browse the repository at this point in the history
  • Loading branch information
iabudiab committed Jan 28, 2023
1 parent a9ee979 commit 9b84281
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 51 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 0.13.0

### New

- Use structured concurrency instead of EventLoops for watching resources #25

### Fixes

- Fix client crash in `GenericKubernetesClient.prepareDecoder` due to decoder instance being shared #24

### Breaking Changes

- Drop AnyKubernetesAPIResource in favour of UnstructuredResource
- The generic client for a given `GroupVersionResource` uses `UnstructuredResource` for unknown types:
- `KubernetesClient/``for``(gvr:) -> GenericKubernetesClient<AnyKubernetesAPIResource>` is replaced with:
- `KubernetesClient/``for``(gvr:) -> GenericKubernetesClient<UnstructuredResource>`
- The `SwiftkubeClientTask` returned by the `watch` and `follow` API must be started explicitly
- The `SwiftkubeClientTask` returns an `AsyncThrowingStream` instead of using a callback delegate

## 0.12.0

### New
Expand Down
204 changes: 153 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
* [Configuring a client](#configuring-the-client)
* [Client authentication](#client-authentication)
* [Client DSL](#client-dsl)
* [Advanced usage](#advanced-usage)
* [Loading from external sources](#loading-from-external-sources)
* [Type-erased usage](#type-erased-usage)
* [CRD Support](#crd-support)
* [Metrics](#metrics)
* [Installation](#installation)
* [License](#license)
Expand Down Expand Up @@ -58,31 +60,29 @@ on [SwiftNIO](https://github.com/apple/swift-nio) and the [AysncHTTPClient](http
- [x] Resource watch support
- [x] Follow pod logs support
- [x] Discovery API
- [ ] CRD support
- [x] CRD support
- [ ] Controller/Informer support
- [x] Swift Metrics
- [ ] Complete documentation
- [ ] End-to-end tests

## Compatibility Matrix

| | <1.18.9 | 1.18.9 - 1.18.13 | 1.19.8 | 1.20.9 | 1.22.7 | 1.24.8 |
|------------------------|---------|------------------|--------|--------|--------|--------|
| SwiftkubeClient 0.6.x | - || - | - | - | - |
| SwiftkubeClient 0.7.x | - | - || - | - | - |
| SwiftkubeClient 0.8.x | - | - || - | - | - |
| SwiftkubeClient 0.9.x | - | - || - | - | - |
| SwiftkubeClient 0.10.x | - | - | - || - | - |
| SwiftkubeClient 0.11.x | - | - | - | - || - |
| SwiftkubeClient 0.12.x | - | - | - | - | - ||
| | 1.18.9 | 1.19.8 | 1.20.9 | 1.22.7 | 1.24.8 |
|-------------------|--------|--------|--------|--------|--------|
| `0.6.x` || - | - | - | - |
| `0.7.x...0.9.x` | - || - | - | - |
| `0.10.x` | - | - || - | - |
| `0.11.x` | - | - | - || - |
| `0.12.x...0.13.x` | - | - | - | - ||

- `` Exact match of API objects in both client and the Kubernetes version.
- `-` API objects mismatches either due to the removal of old API or the addition of new API. However, everything the
- client and Kubernetes have in common will work.

## Examples

Concrete examples for using the `Swiftkube` tooling reside in the[Swiftkube:Examples](https://github.com/swiftkube/examples)
Concrete examples for using the `Swiftkube` tooling reside in the [Swiftkube:Examples](https://github.com/swiftkube/examples)
repository.

## Usage
Expand Down Expand Up @@ -251,14 +251,19 @@ You can watch for Kubernetes events about specific objects via the `watch` API.
Watching resources opens a persistent connection to the API server. The connection is represented by a `SwiftkubeClientTask`
instance, that acts as an active "subscription" to the events stream.

The task can be cancelled any time to stop the watch.
The task instance must be started explicitly via ``SwiftkubeClientTask/start()``, which returns an
``AsyncThrowingStream``, that starts yielding items immediately as they are received from the Kubernetes API server.

> The async stream buffers its results if there are no active consumers. The ``AsyncThrowingStream.BufferingPolicy.unbounded``
buffering policy is used, which should be taken into consideration.

```swift
let task: SwiftkubeClientTask = client.pods.watch(in: .allNamespaces) { (event, pod) in
print("\(event): \(pod)")
}
let task: SwiftkubeClientTask = client.pods.watch(in: .allNamespaces)
let stream = task.start()

task.cancel()
for try await event in stream {
print(event)
}
```

You can also pass `ListOptions` to filter, i.e. select the required objects:
Expand All @@ -269,13 +274,11 @@ let options = [
.labelSelector(.exists(["env"]))
]

let task = client.pods.watch(in: .default, options: options) { (event, pod) in
print("\(event): \(pod)")
}
let task = client.pods.watch(in: .default, options: options)
```

The client reconnects automatically and restarts the watch upon encountering non-recoverable errors. The reconnect
behaviour can be controlled by passing an instance of `RetryStrategy`.
The client reconnects automatically and restarts the watch upon encountering non-recoverable errors. The
reconnect-behaviour can be controlled by passing an instance of `RetryStrategy`.

The default strategy is 10 retry attempts with a fixed 5 seconds delay between each attempt. The initial delay is one
second. A jitter of 0.2 seconds is applied.
Expand All @@ -289,41 +292,29 @@ let strategy = RetryStrategy(
initialDelay = 5.0,
jitter = 0.2
)
let task = client.pods.watch(in: .default, retryStrategy: strategy) { (event, pod) in
print(pod)
let task = client.pods.watch(in: .default, retryStrategy: strategy)

for try await event in task.stream() {
print(event)
}
```

To handle events you can pass a `ResourceWatcherCallback.EventHandler` closure, which is used as a callback for new events.
The client sends each event paired with the corresponding resource as a pair to this `eventHandler`.

If you require more control or stateful logic, then you can implement the `ResourceWatcherDelegate` protocol and pass
it to the `watch` call:
The task must be cancelled when it is no longer needed:

```swift
class MyDelegate: ResourceWatcherDelegate {
typealias Resource = core.v1.Pod

func onEvent(event: EventType, resource: core.v1.Pod) {
// handle events
}

func onError(error: SwiftkubeClientError) {
// handle errors
}
}

let task = client.pods.watch(in: .default, delegate: MyDelegate())
task.cancel()
```

#### Follow logs

The `follow` API resembles the `watch`. The difference being the closure/delegate signature:
The `follow` API resembles the `watch`, but instead of events, it emits the log lines.

:warning: The client does not reconnect on errors in `follow` mode.

```swift
let task = client.pods.follow(in: .default, name: "nginx", container: "app") { (line) in
let task = client.pods.follow(in: .default, name: "nginx", container: "app")

for try await line in task.start() {
print(line)
}

Expand All @@ -342,8 +333,6 @@ let groups: meta.v1.APIGroupList = try await client.discovery.serverGroups()
let resources: meta.v1.APIResourceList = try await client.discovery.serverResources(forGroupVersion: "apps/v1")
```

## Advanced usage

### Loading from external sources

A resource can be loaded from a file or a URL:
Expand All @@ -360,8 +349,8 @@ Often when working with Kubernetes the concrete type of the resource is not know
resources from a YAML manifest file. Other times the type or kind of the resource must be derived at runtime given its
string representation.

Leveraging `SwiftkubeModel`'s type-erased resource implementations `AnyKubernetesAPIResource` and its corresponding
List-Type `AnyKubernetesAPIResourceList` it is possible to have a generic client instance, which must be initialized
Leveraging `SwiftkubeModel`'s type-erased resource implementations `UnstructuredResource` and its corresponding
List-Type `UnstructuredResourceList` it is possible to have a generic client instance, which must be initialized
with a `GroupVersionResource` type:

```swift
Expand All @@ -370,10 +359,10 @@ guard let gvr = try? GroupVersionResource(for: "deployment") else {
}

// Get by name
let resource: AnyKubernetesAPIResource = try await client.for(gvr: gvr).get(in: .default , name: "nginx")
let resource: UnstructuredResource = try await client.for(gvr: gvr).get(in: .default , name: "nginx")

// List all
let resources: AnyKubernetesAPIResourceList = try await client.for(gvr: gvr).list(in: .allNamespaces)
let resources: UnstructuredResourceList = try await client.for(gvr: gvr).list(in: .allNamespaces)
```

#### GroupVersionKind & GroupVersionResource
Expand All @@ -398,6 +387,120 @@ let gvr = GroupVersionResource(for: "cm")
// etc.
```

### CRD Support

`SwiftkubeClient` supports Custom Resource Definitions (CRDs) natively. For example, a CRD manifest can be loaded
from a YAML file or created programmatically, and then created via the client DSL:

```swift
let crd = apiextensions.v1.CustomResourceDefinition.load(contentsOf: URL(filePath: "/path/to/crd.yaml"))
try await client.apiExtensionsV1.customResourceDefinitions.create(crd)
```

The `KubernetesClient` can now be "extended", in order to manage the Custom Resources. One way would be to use the
`UnstructuredResource` described in the previous section given some `GroupVersionResource`.

However, the client can work with any object that implement the relevant marker protocols, which allows for custom types
to be defined and used directly.

Here is a complete example to clarify.

Given the following CRD:

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.example.com
spec:
group: example.com
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
```
The marker protocols are:
- `KubernetesAPIResource` marks the object as a Kubernetes resource that has a corresponding API endpoint
- `NamespacedResource` & `ClusterScopedResource` to indicate whether the resource is namespaced or cluster-scoped
- `ReadableResource` activates the `get`, `list` and `watch` API for the resource
- `CreatableResource` activates the `create` API for the resource
- `ReplaceableResource` activates the `update` API for the resource
- `DeletableResource` activates the `delete` API for the resource
- `CollectionDeletableResource` activate the `deleteAll` API for the resource
- `ScalableResource` activates the `scale` API for the resource
- `MetadataHavingResource` indicates, that the resource has a `metadata` field of type `meta.v1.ObjectMeta?`
- `StatusHavingResource` indicate, that the resource has a `scale` field (w/o assuming its type)

The following custom structs can be defined:

```swift
struct CronTab: KubernetesAPIResource, NamespacedResource, MetadataHavingResource,
ReadableResource, CreatableResource, ListableResource {
typealias List = CronTabList
var apiVersion = "stable.example.com/v1"
var kind = "CronTab"
var metadata: meta.v1.ObjectMeta?
var spec: CronTabSpec
}
struct CronTabSpec: Codable {
var cronSpec: String
var image: String
var replicas: Int
}
struct CronTabList: KubernetesResourceList {
var apiVersion = "stable.example.com/v1"
var kind = "crontabs"
var items: [CronTab]
}
```

Now, the new Custom Resource can be used like any other Kubernetes resource:

```swift
let gvr = GroupVersionResource(
group: "example.swiftkube.dev",
version: "v1",
resource: "cocktails"
)
let cocktailsClient = client.for(Cocktail.self, gvr: gvr)
let cronTab = CronTab(
metadata: meta.v1.ObjectMeta(name: "gin-tonic"),
spec: CocktailSpec(
name: "Basic Gin Tonic",
ingredients: ["Gin", "Tonic"]
)
)
let new = try await cocktailsClient.create(in: .default, cronTab)
let cronTabs: CronTabList = try await cocktailsClient.list(in: .allNamespaces)
```

## Metrics

`KubernetesClient` uses [SwiftMetrics](https://github.com/apple/swift-metrics) to collect metric information about the
Expand All @@ -410,7 +513,6 @@ The following metrics are gathered:
- `sk_request_errors_total(counter)`: the total number of requests that couldn't be dispatched due to non-http errors.
- `sk_http_request_duration_seconds(timer)`: the complete request durations.


### Collecting the metrics

To collect the metrics you have to bootstrap a metrics backend in your application. For example, you can collect the
Expand Down

0 comments on commit 9b84281

Please sign in to comment.