swiftkube/model
## Table of contents
Table of contents
Overview
SwiftkubeModel is a zero-dependency Swift package for Kubernetes API objects.
- [x] Model structs for all Kubernetes objects
- [x]
Codablesupport - [x]
Hashableresources - [x]
Sendablesupport - [x] Closure-based builders for convenient object composition
- [x] Type-erased wrappers for Kubernetes resources
- [x]
UnstructuredResourcetype for handling any Kubernetes resource
Compatibility Matrix
| | 1.26.4 | 1.28.0 | 1.28.3 | 1.29.6 | 1.32.0 | 1.32.2 | 1.33.3 | 1.34.6 | |----------|--------|--------|--------|--------|--------|--------|--------|--------| | 0.10.x | ✓ | - | - | - | - | - | - | - | | 0.11.x | ✓ | - | - | - | - | - | - | - | | 0.12.x | - | ✓ | - | - | - | - | - | - | | 0.13.x | - | - | ✓ | ✓ | - | - | - | - | | 0.14.x | - | - | - | ✓ | - | - | - | - | | 0.15.x | - | - | - | - | ✓ | - | - | - | | 0.16.x | - | - | - | - | ✓ | - | - | - | | 0.17.x | - | - | - | - | - | ✓ | - | - | | 0.18.x | - | - | - | - | - | - | ✓ | - | | 0.19.x | - | - | - | - | - | - | - | ✓ |
✓Exact match of API objects in both model and the Kubernetes version.-API objects mismatches either due to the removal of old API or the addition of new API. However, everything the- model and Kubernetes have in common will work.
Usage
To use the Kubernetes objects just import `SwiftkubeModel`:
```swift
import SwiftkubeModel
let metadata = meta.v1.ObjectMatadata(name: "swiftkube")
let pod = core.v1.Pod(metadata: metadata)
```
All the objects are namespaced according to their API group and version, e.g. `apps.v1.Deployment` or
`networking.v1beta1.Ingress`. Which means, that for example `rbac.v1.Role` and `rbac.v1beta1.Role` are completely
different objects.
### Examples
Any Kubernetes object can be constructed directly using the model structs. Here is an example for a `Deployment` manifest:
```swift
let deployment = apps.v1.Deployment(
metadata: meta.v1.ObjectMeta(
name: "nginx"
),
spec: apps.v1.DeploymentSpec(
replicas: 1,
selector: meta.v1.LabelSelector(
matchLabels: ["app": "nginx"]
),
template: core.v1.PodTemplateSpec (
spec: core.v1.PodSpec(
containers: [
core.v1.Container(
image: "nginx",
name: "nginx",
)
]
)
)
)
)
```
Here is a `ConfigMap`:
```swift
let configMap = core.v1.ConfigMap(
metadata: meta.v1.ObjectMeta(
name: "config"
),
data: [
"env": "dev",
"log_leve": "debug"
]
)
```
A more complete example of a `Deployment`, that defines `Probes`, `ResourceRequirements`, `Volumes` and `VolumeMounts`
would look something like this:
```swift
let deployment = apps.v1.Deployment(
metadata: meta.v1.ObjectMeta(
name: "opa",
namespace: "default"
),
spec: apps.v1.DeploymentSpec(
replicas: 2,
selector: meta.v1.LabelSelector(
matchLabels: ["app": "opa"]
),
template: core.v1.PodTemplateSpec (
spec: core.v1.PodSpec(
containers: [
core.v1.Container(
image: "openpolicyagent/opa",
name: "opa",
readinessProbe: core.v1.Probe(
failureThreshold: 1,
httpGet: core.v1.HTTPGetAction(
path: "/health",
port: 8080
),
initialDelaySeconds: 10,
periodSeconds: 20,
successThreshold: 2,
timeoutSeconds: 5
),
resources: core.v1.ResourceRequirements(
limits: [
"ram": "512MB"
],
requests: [
"ram": "128MB",
"cpu": "200m",
]
),
volumeMounts: [
core.v1.VolumeMount(
mountPath: "/etc/test",
name: "data"
)
]
)
],
imagePullSecrets: [
core.v1.LocalObjectReference(name: "secret-name")
],
volumes: [
core.v1.Volume(
name: "data",
persistentVolumeClaim: core.v1.PersistentVolumeClaimVolumeSource(
claimName: "pvc",
readOnly: true
)
)
]
)
)
)
)
```
### Sendables
All resources are `Sendable` structs.
There is, however, one caveat when working with resources having a `JSONObject` field:
- `apiextensions.v1.CustomResourceValidation`
- `apps.v1.ControllerRevision`
- `meta.v1.ManagedFieldsEntry`
- `meta.v1.WatchEvent`
- `resource.v1alpha3.AllocatedDeviceStatus`
- `resource.v1alpha3.OpaqueDeviceConfiguration`
or when working with `UnstructuredResource`.
They store their properties as `Dictionary<String, any Sendable>`. Thus, dictionary literals must be
explicitly cast to `[String: any Sendable]`.
For example:
```swift
UnstructuredResource(properties: [
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": meta.v1.ObjectMeta(name: "configs", namespace: "default"),
"data": [
"foo": 42,
"bar": "baz"
] as [String: any Sendable]
])
```
### Builders
From the above example it is clear, that a certain knowledge of all the subtypes and their API groups is required, in
order to comose a complete manifest. Furthermore, Swift doesn't allow arbitrary arguments order.
For this purpose `SwiftkubeModel` provides simple closure-based builder functions for convenience. All these functions
reside under the `sk` namespace.
> :warning: The syntax is not yet finalized and can break many times before v1.0.0 ships. This can also be replaced
> with Function/Result Builders, which is currently a WIP.
> :warning: `SwiftkubeModel` currently provides convenience builders only for the most common Kubernetes objects.
The above example would look like this:
```swift
let deployment = sk.deployment(name: "opa") {
$0.metadata = sk.metadata {
$0.namespace = "default"
}
$0.spec = sk.deploymentSpec {
$0.replicas = 1
$0.selector = sk.match(labels: ["app": "nginx"])
$0.template = sk.podTemplate {
$0.spec = sk.podSpec {
$0.containers = [
sk.container(name: "opa") {
$0.image = "openpolicyagent/opa"
$0.readinessProbe = sk.probe(action: .httpGet(path: "/health", port: 8080)) {
$0.failureThreshold = 1
$0.initialDelaySeconds = 10
$0.periodSeconds = 20
$0.successThreshold = 2
$0.failureThreshold = 5
}
$0.resources = sk.requirements {
$0.requests = [
"ram": "512MB"
]
$0.limits = [
"ram": "128MB",
"cpu": "200m",
]
}
$0.volumeMounts = [
sk.volumeMount(name: "data", mountPath: "/etc/test")
]
}
]
$0.imagePullSecrets = [
sk.localObjectReference(name: "secret-name")
]
$0.volumes = [
sk.volume(name: "data", from: .persistentVolumeClaim(claimName: "pvc", readOnly: true))
]
}
}
}
}
```
### Extensions
In addition to closure-based builders, `SwiftkubeModel` extends the Model objects with some convenience functions,
*inspired by [cdk8s](https://cdk8s.io)*
#### core.v1.ConfigMap
- Populating a `ConfigMap`
```swift
let configMap: core.v1.ConfigMap = sk.configMap(name: "test")
// populate the config map
configMap.add(data: "stuff", forKey: "foo")
configMap.add(binaryData: <binary>, forKey: "foo")
configMap.add(file: URL(fileURLWithPath: "/some/path"), forKey: "foo")
configMap.add(binaryFile: URL(fileURLWithPath: "/some/path"), forKey: "foo")
```
### core.v1.Container
- Mount a volume in a container
```swift
let container: core.v1.Container = ...
let volume: core.v1.Volume = ...
// mount a volume in a container
container.mount(volume: volume, on: "/data")
container.mount(volume: "dataVolume", on: "/data")
```
### core.v1.Namespace
- Finalizers
```swift
let namespace: core.v1.Namespace = ...
// add/remove finalizers
namespace.add(finalizer: "foo")
namespace.remove(finalizer: "foo")
```
#### core.v1.Secret
- Populating a `Secret`: the values are Base64-encoded automatically
```swift
let secret: core.v1.Secret = sk.secret(name: "test")
// populate the secret
configMap.add(data: "stuff", forKey: "foo")
configMap.add(file: URL(fileURLWithPath: "/some/path"), forKey: "foo")
```
#### core.v1.Service
- Server ports on a service
```swift
let service: core.v1.Service = ...
// add a service port entry
service.serve(port: 8080, targetPort: 80)
```
#### core.v1.ServiceAccount
- Use secrets
```swift
let serviceAccount: core.v1.ServiceAccount = ...
// add an object reference for a secret
serviceAccount.use(imagePullSecret: "pullSecret")
serviceAccount.use(secret: "secret", namespace: "ns")
```
#### apps.v1.Deployment
- Exposing a `Deployment`
```swift
let deployment: apps.v1.Deployment = ...
// expose a deployment instance to create a service
let service = deployment.expose(on: 8080, type: .clusterIP)
```
### Type-erasure
Often when working with Kubernetes the concrete type of the resource is not known or not relevant, e.g. when creating
resources from a YAML manifest file. Other times the type or kind of the resource must be derived at runtime given its
string representation.
`SwiftkubeModel` provides a type-erased resource implementation `UnstructuredResource` and its corresponding
List-Type `UnstructuredResourceList` in order to tackle these use-cases.
`UnstruturedResource` allows objects that do not have registered `KubernetesAPIResource`s to be manipulated generically.
This can be used to deal with the API objects from a plug-in or CRDs.
Here are some examples to clarify their purpose:
```swift
// Given a JSON string, e.g. at runtime, containing some Kubernetes resource
let json = """
{
"apiVersion": "stable.example.com/v1",
"kind": "CronTab",
"metadata": {
"name": "my-new-cron-object",
"namespace": "default"
},
"spec": {
"cronSpec": "* * * * */5",
"image": "my-awesome-cron-image"
}
}
"""
// We can still decode it without knowing the concrete type
let data = str.data(using: .utf8)!
let resource = try? JSONDecoder().decode(UnstructuredResource.self, from: data)
// When encoding the previous instance, it serializes the underlying resource
let encoded = try? JSONEncoder().encode(resource)
```
The `UnstruturedResource` exposes its internal dictionary representation and also provides a dynamic subscript support:
```swift
let json = """
{
"apiVersion": "stable.example.com/v1",
"kind": "CronTab",
"metadata": {
"name": "my-new-cron-object",
"namespace": "default"
},
"spec": {
"cronSpec": "* * * * */5",
"image": "my-awesome-cron-image"
}
}
"""
let data = str.data(using: .utf8)!
let cron = try? JSONDecoder().decode(UnstructuredResource.self, from: data)
// Shortcut vars
print(cron.apiVersion)
print(cron.kind)
print(cron.metadata)
// The internal Dictionary<String: Any> representation
print(cron.properties)
// Dynamic member lookup
let spec: [String: Any]? = cron.spec
print(spec?["cronSpec"])
```Installation
To use the SwiftkubeModel in a SwiftPM project, add the following line to the dependencies in your Package.swift file:
.package(name: "SwiftkubeModel", url: "https://github.com/swiftkube/model.git", from: "0.18.0")then include it as a dependency in your target:
import PackageDescription
let package = Package(
// ...
dependencies: [
.package(name: "SwiftkubeModel", url: "https://github.com/swiftkube/model.git", from: "0.18.0")
],
targets: [
.target(name: "<your-target>", dependencies: [
.product(name: "SwiftkubeModel", package: "SwiftkubeModel"),
])
]
)Then run swift build.
License
Swiftkube project is licensed under version 2.0 of the Apache License. See LICENSE for more details.
Package Metadata
Repository: swiftkube/model
Default branch: main
README: README.md