Skip to content

Commit 3844e03

Browse files
authored
Merge pull request #321 from stefanprodan/immutable-cfg-gen
Add `#ImmutableConfig` generator to Timoni's CUE schemas
2 parents cc8e65b + 106f6a3 commit 3844e03

File tree

10 files changed

+383
-59
lines changed

10 files changed

+383
-59
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Stefan Prodan
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"encoding/json"
8+
"strings"
9+
"uuid"
10+
)
11+
12+
#ConfigMapKind: "ConfigMap"
13+
#SecretKind: "Secret"
14+
15+
// ImmutableConfig is a generator for immutable Kubernetes ConfigMaps and Secrets.
16+
// The metadata.name of the generated object is suffixed with the hash of the input data.
17+
#ImmutableConfig: {
18+
// Kind of the generated object.
19+
#Kind: *#ConfigMapKind | #SecretKind
20+
21+
// Metadata of the generated object.
22+
#Meta: #Metadata
23+
24+
// Optional suffix appended to the generate name.
25+
#Suffix: *"" | string
26+
27+
// Data of the generated object.
28+
#Data: {[string]: string}
29+
30+
let hash = strings.Split(uuid.SHA1(uuid.ns.DNS, json.Marshal(#Data)), "-")[0]
31+
32+
apiVersion: "v1"
33+
kind: #Kind
34+
metadata: {
35+
name: #Meta.name + #Suffix + "-" + hash
36+
namespace: #Meta.namespace
37+
labels: #Meta.labels
38+
if #Meta.annotations != _|_ {
39+
annotations: #Meta.annotations
40+
}
41+
}
42+
immutable: true
43+
if kind == #ConfigMapKind {
44+
data: #Data
45+
}
46+
if kind == #SecretKind {
47+
stringData: #Data
48+
}
49+
}

docs/cue/module/immutable-config.md

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Immutable ConfigMaps and Secrets
2+
3+
Timoni offers a CUE definition `#ImmutableConfig` for generating immutable Kubernetes ConfigMaps and Secrets.
4+
5+
When the ConfigMap or Secret data changes, Timoni will create a new object with a new name suffix,
6+
and it will update the references to the new object, triggering a rolling update for the
7+
application's Deployments, StatefulSets, DaemonSets, etc.
8+
The old ConfigMaps and Secrets will be deleted from the cluster after the rolling update is completed.
9+
10+
## Example
11+
12+
Assuming you want to populate the app Deployment environment variables from a Kubernetes Secret,
13+
with data that end-users can set at installation and upgrade time.
14+
15+
### Create the `Secret` template
16+
17+
In the `templates` directory, create a `secret.cue` file with the following content:
18+
19+
```cue
20+
package templates
21+
22+
import (
23+
timoniv1 "timoni.sh/core/v1alpha1"
24+
)
25+
26+
#Secret: timoniv1.#ImmutableConfig & {
27+
#config: #Config
28+
#Kind: timoniv1.#SecretKind
29+
#Meta: #config.metadata
30+
#Data: {
31+
"LOGGING_LEVEL_ROOT": #config.logLevel
32+
}
33+
}
34+
35+
```
36+
37+
The `#ImmutableConfig` definition will generate an immutable `Secret` resource with the
38+
`metadata.name` set to`<instance-name>-<data-hash>`, where `<data-hash>` is a hash
39+
of the `#Data` object. This ensures that the `Secret` name will change when the
40+
`#Data` content changes.
41+
42+
!!! tip "ConfigMap generator"
43+
44+
If you want to generate a Kubernetes ConfigMap instead of a Secret,
45+
set the `#Kind` to `timoniv1.#ConfigMapKind`.
46+
47+
If you want to generate multiple ConfigMaps and Secrets, to avoid name collisions,
48+
set the `#Suffix` to a unique string, e.g. `#Suffix: "-cm1"`.
49+
50+
### Reference the `Secret` in the `Deployment` template
51+
52+
In the `templates/deployment.cue` file, define the `secretName` as an input parameter,
53+
and reference it in `envFrom`:
54+
55+
```cue
56+
#Deployment: appsv1.#Deployment & {
57+
#config: #Config
58+
#secretName: string
59+
60+
spec: {
61+
template: {
62+
spec: {
63+
containers: [{
64+
envFrom: [{
65+
secretRef: {
66+
name: #secretName
67+
}
68+
}]
69+
}]
70+
}
71+
}
72+
}
73+
}
74+
75+
```
76+
77+
We need to pass the `secretName` to the `Deployment` template so that every time the
78+
`Secret` name changes, the `Deployment` spec will be updated with the new name.
79+
80+
### Add the `logLevel` to the `Config` definition
81+
82+
In the `templates/config.cue` file, add the `logLevel` configuration:
83+
84+
```cue
85+
#Config: {
86+
logLevel: *"INFO" | "DEBUG" | "WARN" | "ERROR"
87+
}
88+
```
89+
90+
### Add the `Secret` to the `Instance` definition
91+
92+
In the `templates/config.cue` file, add the `Secret` resource to the instance objects,
93+
and pass the generated `secret.metadata.name` to the `Deployment` template:
94+
95+
```cue
96+
#Instance: {
97+
config: #Config
98+
99+
objects: {
100+
secret: #Secret & {#config: config}
101+
102+
deploy: #Deployment & {
103+
#config: config
104+
#secretName: secret.metadata.name
105+
}
106+
}
107+
}
108+
109+
```

docs/quickstart.md

+111-35
Original file line numberDiff line numberDiff line change
@@ -35,40 +35,90 @@ you have to specify the container registry address and the version of a module.
3535
For example, to install the latest stable version of [podinfo](https://github.com/stefanprodan/podinfo)
3636
in a new namespace:
3737

38-
```console
39-
$ timoni -n test apply podinfo oci://ghcr.io/stefanprodan/modules/podinfo --version latest
40-
pulling oci://ghcr.io/stefanprodan/modules/podinfo:latest
41-
using module timoni.sh/podinfo version 6.5.4
42-
installing podinfo in namespace test
43-
Namespace/test created
44-
ServiceAccount/test/podinfo created
45-
Service/test/podinfo created
46-
Deployment/test/podinfo created
47-
waiting for 3 resource(s) to become ready...
48-
all resources are ready
49-
```
38+
=== "command"
39+
40+
```shell
41+
timoni -n test apply podinfo oci://ghcr.io/stefanprodan/modules/podinfo
42+
```
43+
44+
=== "output"
45+
46+
```text
47+
pulling oci://ghcr.io/stefanprodan/modules/podinfo:latest
48+
using module timoni.sh/podinfo version 6.5.4
49+
installing podinfo in namespace test
50+
Namespace/test created
51+
ServiceAccount/test/podinfo created
52+
Service/test/podinfo created
53+
Deployment/test/podinfo created
54+
waiting for 3 resource(s) to become ready...
55+
all resources are ready
56+
```
57+
58+
The apply command pulls the module from the container registry,
59+
creates the Kubernetes resources in the specified namespace,
60+
and waits for all resources to become ready.
61+
62+
To learn more about all the available apply options, use `timoni apply --help`.
5063

5164
## List and inspect instances
5265

53-
You can list all instances in a cluster with `timoni ls -A`.
66+
You can list all instances in a cluster with:
5467

55-
To get more information on an instance, you can use the `timoni inspect` sub-commands:
68+
=== "command"
5669

57-
```console
58-
$ timoni -n test inspect module podinfo
59-
name: timoni.sh/podinfo
60-
version: 6.5.4
61-
repository: oci://ghcr.io/stefanprodan/modules/podinfo
62-
digest: sha256:1dba385f9d56f9a79e5b87344bbec1502bd11f056df51834e18d3e054de39365
63-
```
70+
```shell
71+
timoni list -A
72+
```
6473

65-
To learn more about the available commands, use `timoni inspect --help`.
74+
=== "output"
75+
76+
```text
77+
NAME NAMESPACE MODULE VERSION LAST APPLIED BUNDLE
78+
podinfo test oci://ghcr.io/stefanprodan/modules/podinfo 6.5.4 2024-01-20T19:51:17Z -
79+
```
6680

6781
To see the status of the Kubernetes resources managed by an instance:
6882

69-
```shell
70-
timoni -n test status podinfo
71-
```
83+
=== "command"
84+
85+
```shell
86+
timoni -n test status podinfo
87+
```
88+
89+
=== "output"
90+
91+
```text
92+
last applied 2024-01-20T19:51:17Z
93+
module oci://ghcr.io/stefanprodan/modules/podinfo:6.5.4
94+
digest sha256:1dba385f9d56f9a79e5b87344bbec1502bd11f056df51834e18d3e054de39365
95+
container image ghcr.io/curl/curl-container/curl-multi:master
96+
container image ghcr.io/stefanprodan/podinfo:6.5.4
97+
ServiceAccount/test/podinfo Current - Resource is current
98+
Service/test/podinfo Current - Service is ready
99+
Deployment/test/podinfo Current - Deployment is available. Replicas: 1
100+
```
101+
102+
To get more information on an instance, you can use the `timoni inspect` sub-commands.
103+
104+
For example, to list the module URL, version and OCI digest of the podinfo instance:
105+
106+
=== "command"
107+
108+
```shell
109+
timoni -n test inspect module podinfo
110+
```
111+
112+
=== "output"
113+
114+
```text
115+
digest: sha256:1dba385f9d56f9a79e5b87344bbec1502bd11f056df51834e18d3e054de39365
116+
name: timoni.sh/podinfo
117+
repository: oci://ghcr.io/stefanprodan/modules/podinfo
118+
version: 6.5.4
119+
```
120+
121+
To learn more about the available commands, use `timoni inspect --help`.
72122

73123
## Configure a module instance
74124

@@ -89,24 +139,50 @@ values: {
89139

90140
Apply the config to the podinfo module to perform an upgrade:
91141

92-
```shell
93-
timoni -n test apply podinfo \
94-
oci://ghcr.io/stefanprodan/modules/podinfo \
95-
--values qos-values.cue
96-
```
142+
=== "command"
143+
144+
```shell
145+
timoni -n test apply podinfo oci://ghcr.io/stefanprodan/modules/podinfo \
146+
--values qos-values.cue
147+
```
148+
149+
=== "output"
150+
151+
```text
152+
pulling oci://ghcr.io/stefanprodan/modules/podinfo:latest
153+
using module timoni.sh/podinfo version 6.5.4
154+
upgrading podinfo in namespace test
155+
ServiceAccount/test/podinfo unchanged
156+
Service/test/podinfo unchanged
157+
Deployment/test/podinfo configured
158+
resources are ready
159+
```
97160

98161
Before running an upgrade, you can review the changes that will
99162
be made on the cluster with `timoni apply --dry-run --diff`.
100163

101-
To learn more about all the available apply options, use `timoni apply --help`.
102-
103164
## Uninstall a module instance
104165

105166
To uninstall an instance and delete all the managed Kubernetes resources:
106167

107-
```shell
108-
timoni -n test delete podinfo --wait
109-
```
168+
=== "command"
169+
170+
```shell
171+
timoni -n test delete podinfo
172+
```
173+
174+
=== "output"
175+
176+
```text
177+
deleting 3 resource(s)...
178+
Deployment/test/podinfo deleted
179+
Service/test/podinfo deleted
180+
ServiceAccount/test/podinfo deleted
181+
all resources have been deleted
182+
```
183+
184+
By default, the delete command will wait for all the resources to be removed.
185+
To skip waiting, use the `--wait=false` flag.
110186

111187
## Bundling instances
112188

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2024 Stefan Prodan
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"encoding/json"
8+
"strings"
9+
"uuid"
10+
)
11+
12+
#ConfigMapKind: "ConfigMap"
13+
#SecretKind: "Secret"
14+
15+
// ImmutableConfig is a generator for immutable Kubernetes ConfigMaps and Secrets.
16+
// The metadata.name of the generated object is suffixed with the hash of the input data.
17+
#ImmutableConfig: {
18+
// Kind of the generated object.
19+
#Kind: *#ConfigMapKind | #SecretKind
20+
21+
// Metadata of the generated object.
22+
#Meta: #Metadata
23+
24+
// Optional suffix appended to the generate name.
25+
#Suffix: *"" | string
26+
27+
// Data of the generated object.
28+
#Data: {[string]: string}
29+
30+
let hash = strings.Split(uuid.SHA1(uuid.ns.DNS, json.Marshal(#Data)), "-")[0]
31+
32+
apiVersion: "v1"
33+
kind: #Kind
34+
metadata: {
35+
name: #Meta.name + #Suffix + "-" + hash
36+
namespace: #Meta.namespace
37+
labels: #Meta.labels
38+
if #Meta.annotations != _|_ {
39+
annotations: #Meta.annotations
40+
}
41+
}
42+
immutable: true
43+
if kind == #ConfigMapKind {
44+
data: #Data
45+
}
46+
if kind == #SecretKind {
47+
stringData: #Data
48+
}
49+
}

examples/minimal/templates/config.cue

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ import (
103103

104104
deploy: #Deployment & {
105105
#config: config
106-
_cmName: objects.cm.metadata.name
106+
#cmName: objects.cm.metadata.name
107107
}
108108
}
109109

0 commit comments

Comments
 (0)