Compare commits

..

7 Commits

Author SHA1 Message Date
Gary Larizza
ae4aad4993 Update Cue tutorial to use testscript
PROBLEM:

The "Cue" tutorial has hardcoded code blocks and hasn't been
updated to use the automated testscript workflow.

NOTE: This is slightly more complex than normal because we need
to make sure Timoni is installed when we execute the testscripts
due to the fact that we need to execute `timoni mod vendor crds ...`
and capture the output.

SOLUTION:

* Add Timoni as one of the packages that are installed via `make go-deps`.
* Update the testing GH Action to install all go dependencies before executing the tests.
* Create a test for the Cue tutorial.
* Create a testscript for the Cue test.
* Update the Cue MDX file to load in data from the testscript directory.

OUTCOME:

The code content in the Cue tutorial now comes directly from the
testscript workflow.
2025-01-23 15:49:04 -08:00
Gary Larizza
410b882d1d Merge pull request #403 from holos-run/gl/hello-holos-testscript
docs: Update Hello Holos tutorial to use testscript
2025-01-22 14:43:47 -08:00
Gary Larizza
e2648202dc Merge pull request #404 from holos-run/gl/kustomize-testscript
docs: Update Kustomize tutorial to use testscript
2025-01-22 14:43:33 -08:00
Jeff McCune
44c2fe220a test: fix helm capabilities test
Helm was upgraded in GitHub Actions resulting in an accidental failure
of the test case.
2025-01-17 12:33:28 -08:00
Jeff McCune
fe1ae2fa80 docs: migrate from an ApplicationSet blog post 2025-01-17 12:22:56 -08:00
Gary Larizza
8fbee1cbd9 docs: Update Kustomize tutorial to use testscript
PROBLEM:

The "Kustomize" tutorial has hardcoded code blocks and hasn't been
updated to use the automated testscript workflow.

SOLUTION:

Create a test for the Kustomize tutorial.
Create a testscript for the Kustomize test.
Update the Kustomize MDX file to load in data from the testscript directory.

OUTCOME:

The code content in the Kustomize tutorial now comes directly from the
testscript workflow.
2025-01-16 14:24:24 -08:00
Gary Larizza
982db2cccc docs: Update Hello Holos tutorial to use testscript
PROBLEM:

The "Hello Holos" tutorial has hardcoded code blocks and hasn't been
updated to use the automated testscript workflow.

SOLUTION:

* Create a test for the Hello Holos tutorial.
* Create a testscript for the Hello Holos test.
* Update the Hello Holos MDX file to load in data from the testscript directory.

OUTCOME:

The code content in the Hello Holos tutorial now comes directly from the
testscript workflow.
2025-01-16 10:12:17 -08:00
154 changed files with 3851 additions and 612 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ tmp/
/holos-k3d/
/holos-infra/
node_modules/
.tmp/

View File

@@ -124,6 +124,7 @@ go-deps: ## tool versions pinned in tools.go
go install golang.org/x/tools/cmd/godoc
go install github.com/princjef/gomarkdoc/cmd/gomarkdoc
go install github.com/google/ko
go install github.com/stefanprodan/timoni/cmd/timoni@v0.23.0
# curl https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash
.PHONY: frontend-deps

View File

@@ -1,7 +1,9 @@
# https://github.com/holos-run/holos/issues/330
exec holos init platform v1alpha5 --force
# Make sure the helm chart works with plain helm
exec helm template ./components/capabilities/vendor/0.1.0/capabilities
cmp stdout want/helm-template.yaml
stdout 'name: has-foo-v1beta1'
stdout 'kubeVersion: v'
exec holos render platform ./platform
# When no capabilities are specified
cmp deploy/components/capabilities/capabilities.gen.yaml want/when-no-capabilities-specified.yaml

View File

@@ -0,0 +1,7 @@
exec bash -c 'bash -euo pipefail $WORK/command.sh 2>&1'
cmp stdout $WORK/output.txt
-- command.sh --
holos --version
-- output.txt --
0.103.0

View File

@@ -0,0 +1,166 @@
# Set $HOME because:
# - Helm uses it for temporary files
# - Git requires it for setting author name/email globally
env HOME=$WORK/.tmp
chmod 0755 $WORK/update.sh
# Configure git author for testscript execution
exec git config --global user.name 'Holos Docs'
exec git config --global user.email 'hello@holos.run'
exec git config --global init.defaultBranch main
# Remove the tutorial directory if it already exists
exec rm -rf holos-cue-tutorial
# Create and change to the tutorial directory, and then initialize the Holos platform
exec bash -c 'bash -euo pipefail mkdir-and-init.sh'
cd holos-cue-tutorial
# Create the components directory, then combine and execute the multiline
# podinfo component header/body/trailer files
exec bash -c 'bash -euo pipefail $WORK/mkdir-components.sh'
exec cat $WORK/podinfo-component-header.sh $WORK/podinfo-component-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Combine and execute the multiline platform registration header/body/trailer files.
exec cat $WORK/register-components-header.sh $WORK/register-components-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Render and capture output
# NOTE: The [net] condition will test whether external network access is available
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/register-components-output.txt
# Git init and commit
exec bash -c 'bash -euo pipefail $WORK/git-init.sh'
# Combine and execute the mixin component header/body/trailer files
exec cat $WORK/mixin-component-header.sh $WORK/mixin-component-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Import CRDs with Timoni
exec bash -c 'bash -euo pipefail $WORK/import-crds.sh 2>&1'
stdin stdout
exec $WORK/update.sh $WORK/timoni-vendor.txt
# Render platform
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
# Git diff and capture output
exec bash -c 'bash -euo pipefail $WORK/git-diff.sh 2>&1'
stdin stdout
exec $WORK/update.sh $WORK/git.diff
# Clean up the tutorial directory and tmp $HOME directory
cd $WORK
exec rm -rf holos-cue-tutorial
exec rm -rf $HOME
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- mkdir-and-init.sh --
mkdir holos-cue-tutorial && cd holos-cue-tutorial
holos init platform v1alpha5
-- mkdir-components.sh --
mkdir -p components/podinfo
-- import-crds.sh --
timoni mod vendor crds -f https://raw.githubusercontent.com/external-secrets/external-secrets/v0.10.5/deploy/crds/bundle.yaml
-- podinfo-component-header.sh --
cat <<EOF > components/podinfo/podinfo.cue
-- podinfo-component-body.cue --
package holos
// export the component build plan to holos
holos: Component.BuildPlan
// Component is a Helm chart
Component: #Helm & {
Name: "podinfo"
Namespace: "default"
// Add metadata.namespace to all resources with kustomize.
KustomizeConfig: Kustomization: namespace: Namespace
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
}
-- eof-trailer.sh --
EOF
-- register-components-header.sh --
cat <<EOF > platform/podinfo.cue
-- register-components-body.cue --
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}
-- render.sh --
holos render platform
-- git-init.sh --
git init . && git add . && git commit -m initial
-- mixin-component-header.sh --
cat <<EOF > components/podinfo/mixins.cue
-- mixin-component-body.cue --
package holos
// Component fields are unified with podinfo.cue
Component: {
// Concrete values are defined in podinfo.cue
Name: string
Namespace: string
// Resources represents mix-in resources organized as a struct.
Resources: ExternalSecret: (Name): {
// Name is consistent with the component name.
metadata: name: Name
// Namespace is consistent with the component namespace.
metadata: namespace: Namespace
spec: {
// Ensure the target secret name is consistent.
target: name: metadata.name
// Ensure the name in the SecretStore is consistent.
dataFrom: [{extract: {key: metadata.name}}]
refreshInterval: "30s"
secretStoreRef: kind: "SecretStore"
secretStoreRef: name: "default"
}
}
}
-- git-diff.sh --
git diff deploy
-- git.diff --
diff --git a/deploy/components/podinfo/podinfo.gen.yaml b/deploy/components/podinfo/podinfo.gen.yaml
index 6e4aec0..f79e9d0 100644
--- a/deploy/components/podinfo/podinfo.gen.yaml
+++ b/deploy/components/podinfo/podinfo.gen.yaml
@@ -112,3 +112,19 @@ spec:
volumes:
- emptyDir: {}
name: data
+---
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: podinfo
+ namespace: default
+spec:
+ dataFrom:
+ - extract:
+ key: podinfo
+ refreshInterval: 30s
+ secretStoreRef:
+ kind: SecretStore
+ name: default
+ target:
+ name: podinfo

View File

@@ -0,0 +1 @@
holos --version

View File

@@ -0,0 +1 @@
0.103.0

View File

@@ -0,0 +1 @@
EOF

View File

@@ -0,0 +1 @@
git diff deploy

View File

@@ -0,0 +1 @@
git init . && git add . && git commit -m initial

View File

@@ -0,0 +1,24 @@
diff --git a/deploy/components/podinfo/podinfo.gen.yaml b/deploy/components/podinfo/podinfo.gen.yaml
index 6e4aec0..f79e9d0 100644
--- a/deploy/components/podinfo/podinfo.gen.yaml
+++ b/deploy/components/podinfo/podinfo.gen.yaml
@@ -112,3 +112,19 @@ spec:
volumes:
- emptyDir: {}
name: data
+---
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: podinfo
+ namespace: default
+spec:
+ dataFrom:
+ - extract:
+ key: podinfo
+ refreshInterval: 30s
+ secretStoreRef:
+ kind: SecretStore
+ name: default
+ target:
+ name: podinfo

View File

@@ -0,0 +1 @@
timoni mod vendor crds -f https://raw.githubusercontent.com/external-secrets/external-secrets/v0.10.5/deploy/crds/bundle.yaml

View File

@@ -0,0 +1,25 @@
package holos
// Component fields are unified with podinfo.cue
Component: {
// Concrete values are defined in podinfo.cue
Name: string
Namespace: string
// Resources represents mix-in resources organized as a struct.
Resources: ExternalSecret: (Name): {
// Name is consistent with the component name.
metadata: name: Name
// Namespace is consistent with the component namespace.
metadata: namespace: Namespace
spec: {
// Ensure the target secret name is consistent.
target: name: metadata.name
// Ensure the name in the SecretStore is consistent.
dataFrom: [{extract: {key: metadata.name}}]
refreshInterval: "30s"
secretStoreRef: kind: "SecretStore"
secretStoreRef: name: "default"
}
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > components/podinfo/mixins.cue

View File

@@ -0,0 +1,2 @@
mkdir holos-cue-tutorial && cd holos-cue-tutorial
holos init platform v1alpha5

View File

@@ -0,0 +1 @@
mkdir -p components/podinfo

View File

@@ -0,0 +1,19 @@
package holos
// export the component build plan to holos
holos: Component.BuildPlan
// Component is a Helm chart
Component: #Helm & {
Name: "podinfo"
Namespace: "default"
// Add metadata.namespace to all resources with kustomize.
KustomizeConfig: Kustomization: namespace: Namespace
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > components/podinfo/podinfo.cue

View File

@@ -0,0 +1,6 @@
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}

View File

@@ -0,0 +1 @@
cat <<EOF > platform/podinfo.cue

View File

@@ -0,0 +1,3 @@
cached podinfo 6.6.2
rendered podinfo in 1.938665041s
rendered platform in 1.938759417s

View File

@@ -0,0 +1 @@
holos render platform

View File

@@ -0,0 +1,17 @@
3:20PM INF schemas vendored: external-secrets.io/clusterexternalsecret/v1beta1
3:20PM INF schemas vendored: external-secrets.io/clustersecretstore/v1alpha1
3:20PM INF schemas vendored: external-secrets.io/clustersecretstore/v1beta1
3:20PM INF schemas vendored: external-secrets.io/externalsecret/v1alpha1
3:20PM INF schemas vendored: external-secrets.io/externalsecret/v1beta1
3:20PM INF schemas vendored: external-secrets.io/pushsecret/v1alpha1
3:20PM INF schemas vendored: external-secrets.io/secretstore/v1alpha1
3:20PM INF schemas vendored: external-secrets.io/secretstore/v1beta1
3:20PM INF schemas vendored: generators.external-secrets.io/acraccesstoken/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/ecrauthorizationtoken/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/fake/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/gcraccesstoken/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/githubaccesstoken/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/password/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/uuid/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/vaultdynamicsecret/v1alpha1
3:20PM INF schemas vendored: generators.external-secrets.io/webhook/v1alpha1

View File

@@ -0,0 +1,4 @@
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"

View File

@@ -0,0 +1,7 @@
exec bash -c 'bash -euo pipefail $WORK/command.sh 2>&1'
cmp stdout $WORK/output.txt
-- command.sh --
holos --version
-- output.txt --
0.103.0

View File

@@ -0,0 +1,126 @@
# Set $HOME because:
# - Helm uses it for temporary files
# - Git requires it for setting author name/email globally
env HOME=$WORK/.tmp
chmod 0755 $WORK/update.sh
# Configure git author for testscript execution
exec git config --global user.name 'Holos Docs'
exec git config --global user.email 'hello@holos.run'
exec git config --global init.defaultBranch main
# Remove the tutorial directory if it already exists
exec rm -rf holos-tutorial
# Create and change to the tutorial directory, and then initialize the Holos platform
exec bash -c 'bash -euo pipefail $WORK/mkdir-and-init.sh'
cd holos-tutorial
# Create the components directory
exec bash -c 'bash -euo pipefail $WORK/mkdir-components.sh'
# Combine and execute the multiline podinfo component header/body/trailer files
exec cat $WORK/podinfo-component-header.sh ../podinfo-component-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Combine and execute the multiline platform registration header/body/trailer files
exec cat $WORK/register-podinfo-header.sh ../register-podinfo-body.cue ../eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Render the platform, capture stdout, and use update.sh to gate whether the
# output file should be updated.
#
# NOTE: The [net] condition will test whether external network access is available
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/register-components-output.txt
# Generate and update the tree of the tutorial directory (omitting the cue.mod directory)
exec bash -c 'bash -euo pipefail $WORK/tree.sh'
stdin stdout
exec $WORK/update.sh $WORK/tree.txt
# Split the rendered manifest into two separate files to display separately
exec bash -c 'bash -euo pipefail $WORK/split-rendered-manifest.sh $WORK/holos-tutorial/deploy/components/podinfo/podinfo.gen.yaml $WORK'
# Grep for the Hello Holos message and write the output file
exec bash -c 'bash -euo pipefail $WORK/grep-for-message.sh'
stdin stdout
exec $WORK/update.sh $WORK/grepped-output.txt
# Clean up the tutorial directory and tmp $HOME directory
cd $WORK
exec rm -rf holos-tutorial
exec rm -rf $HOME
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- mkdir-and-init.sh --
mkdir holos-tutorial && cd holos-tutorial
holos init platform v1alpha5
-- tree.sh --
tree -L 3 -I cue.mod .
-- mkdir-components.sh --
mkdir -p components/podinfo
-- podinfo-component-header.sh --
cat <<EOF > components/podinfo/podinfo.cue
-- podinfo-component-body.cue --
package holos
// Produce a helm chart build plan.
holos: HelmChart.BuildPlan
HelmChart: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
// Holos marshals Values into values.yaml for Helm.
Values: {
// message is a string with a default value. @tag indicates a value may
// be injected from the platform spec component parameters.
ui: {
message: string | *"Hello World" @tag(greeting, type=string)
}
}
}
-- eof-trailer.sh --
EOF
-- register-podinfo-header.sh --
cat <<EOF > platform/podinfo.cue
-- register-podinfo-body.cue --
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
// Inject a value into the component.
parameters: greeting: "Hello Holos!"
}
-- render.sh --
holos render platform
-- register-components-output.txt --
cached podinfo 6.6.2
rendered podinfo in 1.938665041s
rendered platform in 1.938759417s
-- podinfo-rendered-path.sh --
deploy/components/podinfo/podinfo.gen.yaml
-- split-rendered-manifest.sh --
awk 'BEGIN {RS="---"} NR==1 {print > "service.yaml"} NR==2 {print > "deployment.yaml"}' $1
mv service.yaml $2/rendered-service.yaml
mv deployment.yaml $2/rendered-deployment.yaml
-- grep-for-message.sh --
grep -B2 Hello deploy/components/podinfo/podinfo.gen.yaml
-- grepped-output.txt --
env:
- name: PODINFO_UI_MESSAGE
value: Hello Holos!

View File

@@ -0,0 +1 @@
holos --version

View File

@@ -0,0 +1 @@
0.103.0

View File

@@ -0,0 +1 @@
EOF

View File

@@ -0,0 +1 @@
grep -B2 Hello deploy/components/podinfo/podinfo.gen.yaml

View File

@@ -0,0 +1,3 @@
env:
- name: PODINFO_UI_MESSAGE
value: Hello Holos!

View File

@@ -0,0 +1,2 @@
mkdir holos-tutorial && cd holos-tutorial
holos init platform v1alpha5

View File

@@ -0,0 +1 @@
mkdir -p components/podinfo

View File

@@ -0,0 +1,23 @@
package holos
// Produce a helm chart build plan.
holos: HelmChart.BuildPlan
HelmChart: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
// Holos marshals Values into values.yaml for Helm.
Values: {
// message is a string with a default value. @tag indicates a value may
// be injected from the platform spec component parameters.
ui: {
message: string | *"Hello World" @tag(greeting, type=string)
}
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > components/podinfo/podinfo.cue

View File

@@ -0,0 +1 @@
deploy/components/podinfo/podinfo.gen.yaml

View File

@@ -0,0 +1,2 @@
rendered podinfo in 544.501875ms
rendered platform in 544.608125ms

View File

@@ -0,0 +1,8 @@
package holos
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
// Inject a value into the component.
parameters: greeting: "Hello Holos!"
}

View File

@@ -0,0 +1 @@
cat <<EOF > platform/podinfo.cue

View File

@@ -0,0 +1 @@
holos render platform

View File

@@ -0,0 +1,93 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo
app.kubernetes.io/version: 6.6.2
helm.sh/chart: podinfo-6.6.2
name: podinfo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: podinfo
strategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
annotations:
prometheus.io/port: "9898"
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/name: podinfo
spec:
containers:
- command:
- ./podinfo
- --port=9898
- --cert-path=/data/cert
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_MESSAGE
value: Hello Holos!
- name: PODINFO_UI_COLOR
value: '#34577c'
image: ghcr.io/stefanprodan/podinfo:6.6.2
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
failureThreshold: 3
initialDelaySeconds: 1
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
name: podinfo
ports:
- containerPort: 9898
name: http
protocol: TCP
- containerPort: 9797
name: http-metrics
protocol: TCP
- containerPort: 9999
name: grpc
protocol: TCP
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
failureThreshold: 3
initialDelaySeconds: 1
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
resources:
limits: null
requests:
cpu: 1m
memory: 16Mi
volumeMounts:
- mountPath: /data
name: data
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: data

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo
app.kubernetes.io/version: 6.6.2
helm.sh/chart: podinfo-6.6.2
name: podinfo
spec:
ports:
- name: http
port: 9898
protocol: TCP
targetPort: http
- name: grpc
port: 9999
protocol: TCP
targetPort: grpc
selector:
app.kubernetes.io/name: podinfo
type: ClusterIP

View File

@@ -0,0 +1,3 @@
awk 'BEGIN {RS="---"} NR==1 {print > "service.yaml"} NR==2 {print > "deployment.yaml"}' $1
mv service.yaml $2/rendered-service.yaml
mv deployment.yaml $2/rendered-deployment.yaml

View File

@@ -0,0 +1 @@
tree -L 3 -I cue.mod .

View File

@@ -0,0 +1,17 @@
.
|-- components
| `-- podinfo
| |-- podinfo.cue
| `-- vendor
|-- deploy
| `-- components
| `-- podinfo
|-- platform
| |-- platform.gen.cue
| `-- podinfo.cue
|-- platform.metadata.json
|-- resources.cue
|-- schema.cue
`-- tags.cue
8 directories, 7 files

View File

@@ -0,0 +1,4 @@
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"

View File

@@ -1 +1 @@
0.102.5
0.103.0

View File

@@ -1,3 +1,3 @@
[main e1c6859] add blackbox configuration
[main 1adcd08] add blackbox configuration
1 file changed, 15 insertions(+)
create mode 100644 config/prometheus/blackbox.cue
create mode 100644 components/blackbox.cue

View File

@@ -1,3 +1,2 @@
[main 668706a] integrate blackbox and prometheus together
3 files changed, 1348 insertions(+), 2 deletions(-)
create mode 100644 components/prometheus/values.cue.orig
[main 4221803] integrate blackbox and prometheus together
2 files changed, 4 insertions(+), 2 deletions(-)

View File

@@ -1,4 +1,4 @@
[main 7bc6772] import values
[main 52e90ea] import values
2 files changed, 1815 insertions(+)
create mode 100644 components/blackbox/values.cue
create mode 100644 components/prometheus/values.cue

View File

@@ -1,3 +1,3 @@
rendered blackbox in 146.654292ms
rendered prometheus in 178.845292ms
rendered platform in 178.9115ms
rendered blackbox in 365.936792ms
rendered prometheus in 371.855875ms
rendered platform in 372.109916ms

View File

@@ -1,4 +1,4 @@
[main d144f24] add blackbox and prometheus
[main b5df111] add blackbox and prometheus
5 files changed, 1550 insertions(+)
create mode 100644 components/blackbox/blackbox.cue
create mode 100644 components/prometheus/prometheus.cue

View File

@@ -1,3 +1,5 @@
rendered blackbox in 1.794799666s
rendered prometheus in 1.835097625s
rendered platform in 1.835185792s
cached prometheus-blackbox-exporter 9.0.1
rendered blackbox in 3.825430417s
cached prometheus 25.27.0
rendered prometheus in 4.840089667s
rendered platform in 4.840137792s

View File

@@ -1,2 +1,2 @@
[main 1399737] render integrated blackbox and prometheus manifests
[main 67efe0d] render integrated blackbox and prometheus manifests
2 files changed, 7 insertions(+), 7 deletions(-)

View File

@@ -0,0 +1,7 @@
exec bash -c 'bash -euo pipefail $WORK/command.sh 2>&1'
cmp stdout $WORK/output.txt
-- command.sh --
holos --version
-- output.txt --
0.103.0

View File

@@ -0,0 +1,226 @@
# Set $HOME because:
# - Helm uses it for temporary files
# - Git requires it for setting author name/email globally
env HOME=$WORK/.tmp
chmod 0755 $WORK/update.sh
# Configure git author for testscript execution
exec git config --global user.name 'Holos Docs'
exec git config --global user.email 'hello@holos.run'
exec git config --global init.defaultBranch main
# Remove the tutorial directory if it already exists
exec rm -rf holos-kustomize-tutorial
# Create and change to the tutorial directory, and then initialize the Holos platform
exec bash -c 'bash -euo pipefail $WORK/mkdir-and-init.sh'
cd holos-kustomize-tutorial
# Initialize git
exec bash -c 'bash -euo pipefail $WORK/git-init.sh'
# Create the component directory
exec bash -c 'bash -euo pipefail $WORK/mkdir-component.sh'
# Combine and execute the multiline httpbin component header/body/trailer files
exec cat $WORK/httpbin-component-header.sh $WORK/httpbin-component-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Combine and execute the multiline httpbin yaml header/body/trailer files
exec cat $WORK/httpbin-yaml-header.sh $WORK/httpbin-yaml-body.yaml $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Combine and execute the multiline registration header/body/trailer files
exec cat $WORK/register-component-header.sh $WORK/register-component-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Render the platform, capture stdout, and use update.sh to gate whether the
# output file should be updated.
#
# NOTE: The [net] condition will test whether external network access is available
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/register-component-output.txt
# Git commit and capture output
exec bash -c 'bash -euo pipefail $WORK/git-commit-component.sh'
stdin stdout
exec $WORK/update.sh $WORK/git-commit-component-output.txt
# Export Build Plan and capture output
exec bash -c 'bash -euo pipefail $WORK/cue-export.sh'
stdin stdout
exec $WORK/update.sh $WORK/buildplan-output.cue
# Combine and execute the multiline kustomize patch header/body/trailer files
exec cat $WORK/httpbin-patch-header.sh $WORK/httpbin-patch-body.cue $WORK/eof-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Render the platform and capture output
[net] exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
[net] stdin stdout
exec $WORK/update.sh $WORK/kustomize-patch-render-output.txt
# Git diff and capture output
exec bash -c 'bash -euo pipefail $WORK/git-diff.sh'
stdin stdout
exec $WORK/update.sh $WORK/git.diff
# Git commit and capture output
exec bash -c 'bash -euo pipefail $WORK/git-commit-final.sh'
stdin stdout
exec $WORK/update.sh $WORK/git-commit-final-output.txt
# Clean up the tutorial directory and tmp $HOME directory
cd $WORK
exec rm -rf holos-kustomize-tutorial
exec rm -rf $HOME
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- mkdir-and-init.sh --
mkdir holos-kustomize-tutorial
cd holos-kustomize-tutorial
holos init platform v1alpha5
-- git-init.sh --
git init . && git add . && git commit -m initial
-- mkdir-component.sh --
mkdir -p components/httpbin
-- httpbin-component-header.sh --
cat <<EOF > components/httpbin/httpbin.cue
-- httpbin-component-body.cue --
package holos
// Produce a Kustomize BuildPlan for Holos
holos: Kustomize.BuildPlan
// https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/README.md
Kustomize: #Kustomize & {
KustomizeConfig: {
// Files tells Holos to copy the file from the component path to the
// temporary directory Holos uses for BuildPlan execution.
Files: {
"httpbin.yaml": _
}
CommonLabels: {
"app.kubernetes.io/name": "httpbin"
}
// Kustomization represents a kustomization.yaml file in CUE. Holos
// marshals this field into a `kustomization.yaml` while processing a
// BuildPlan. See
// https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/
Kustomization: {
images: [{name: "mccutchen/go-httpbin"}]
// Use a hidden field to compose patches easily with a struct. Hidden
// fields are not included in exported structures.
_patches: {}
// Convert the hidden struct to a list.
patches: [for x in _patches {x}]
}
}
}
-- eof-trailer.sh --
EOF
-- httpbin-yaml-header.sh --
cat <<EOF > components/httpbin/httpbin.yaml
-- httpbin-yaml-body.yaml --
# https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
template:
spec:
containers:
- name: httpbin
image: mccutchen/go-httpbin
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /status/200
port: http
readinessProbe:
httpGet:
path: /status/200
port: http
resources: {}
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
appProtocol: http
-- register-component-header.sh --
cat <<EOF > platform/httpbin.cue
-- register-component-body.cue --
package holos
Platform: Components: {
httpbin: {
name: "httpbin"
path: "components/httpbin"
}
}
-- git-commit-component.sh --
git add . && git commit -m 'add httpbin'
-- cue-export.sh --
holos cue export --expression holos --out=yaml ./components/httpbin
-- httpbin-patch-header.sh --
cat <<EOF > components/httpbin/patches.cue
-- httpbin-patch-body.cue --
package holos
import "encoding/yaml"
// Mix in a Kustomize patch to the configuration.
Kustomize: KustomizeConfig: Kustomization: _patches: {
probe: {
target: kind: "Service"
target: name: "httpbin"
patch: yaml.Marshal([{
op: "add"
path: "/metadata/annotations/prometheus.io~1probe"
value: "true"
}])
}
}
-- httpbin-component-output.txt --
rendered httpbin in 197.030208ms
rendered platform in 197.416416ms
-- render.sh --
holos render platform
-- git-diff.sh --
git diff
-- git.diff --
diff --git a/deploy/components/httpbin/httpbin.gen.yaml b/deploy/components/httpbin/httpbin.gen.yaml
index 298b9a8..a16bd1a 100644
--- a/deploy/components/httpbin/httpbin.gen.yaml
+++ b/deploy/components/httpbin/httpbin.gen.yaml
@@ -1,6 +1,8 @@
apiVersion: v1
kind: Service
metadata:
+ annotations:
+ prometheus.io/probe: "true"
labels:
app.kubernetes.io/name: httpbin
name: httpbin
-- git-commit-final.sh --
git add . && git commit -m 'annotate httpbin for prometheus probes'

View File

@@ -0,0 +1 @@
holos --version

View File

@@ -0,0 +1 @@
0.103.0

View File

@@ -0,0 +1,36 @@
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: no-name
spec:
artifacts:
- artifact: components/no-name/no-name.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
resources: {}
- kind: File
output: httpbin.yaml
file:
source: httpbin.yaml
validators: []
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
- httpbin.yaml
output: components/no-name/no-name.gen.yaml
kustomize:
kustomization:
labels:
- includeSelectors: false
pairs:
app.kubernetes.io/name: httpbin
patches: []
images:
- name: mccutchen/go-httpbin
resources:
- resources.gen.yaml
- httpbin.yaml
kind: Kustomization
apiVersion: kustomize.config.k8s.io/v1beta1

View File

@@ -0,0 +1 @@
holos cue export --expression holos --out=yaml ./components/httpbin

View File

@@ -0,0 +1 @@
EOF

View File

@@ -0,0 +1,6 @@
[main f0dd632] add httpbin
4 files changed, 113 insertions(+)
create mode 100644 components/httpbin/httpbin.cue
create mode 100644 components/httpbin/httpbin.yaml
create mode 100644 deploy/components/httpbin/httpbin.gen.yaml
create mode 100644 platform/httpbin.cue

View File

@@ -0,0 +1 @@
git add . && git commit -m 'add httpbin'

View File

@@ -0,0 +1,3 @@
[main b120712] annotate httpbin for prometheus probes
2 files changed, 18 insertions(+)
create mode 100644 components/httpbin/patches.cue

View File

@@ -0,0 +1 @@
git add . && git commit -m 'annotate httpbin for prometheus probes'

View File

@@ -0,0 +1 @@
git diff

View File

@@ -0,0 +1 @@
git init . && git add . && git commit -m initial

View File

@@ -0,0 +1,13 @@
diff --git a/deploy/components/httpbin/httpbin.gen.yaml b/deploy/components/httpbin/httpbin.gen.yaml
index 298b9a8..a16bd1a 100644
--- a/deploy/components/httpbin/httpbin.gen.yaml
+++ b/deploy/components/httpbin/httpbin.gen.yaml
@@ -1,6 +1,8 @@
apiVersion: v1
kind: Service
metadata:
+ annotations:
+ prometheus.io/probe: "true"
labels:
app.kubernetes.io/name: httpbin
name: httpbin

View File

@@ -0,0 +1,30 @@
package holos
// Produce a Kustomize BuildPlan for Holos
holos: Kustomize.BuildPlan
// https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/README.md
Kustomize: #Kustomize & {
KustomizeConfig: {
// Files tells Holos to copy the file from the component path to the
// temporary directory Holos uses for BuildPlan execution.
Files: {
"httpbin.yaml": _
}
CommonLabels: {
"app.kubernetes.io/name": "httpbin"
}
// Kustomization represents a kustomization.yaml file in CUE. Holos
// marshals this field into a `kustomization.yaml` while processing a
// BuildPlan. See
// https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/
Kustomization: {
images: [{name: "mccutchen/go-httpbin"}]
// Use a hidden field to compose patches easily with a struct. Hidden
// fields are not included in exported structures.
_patches: {}
// Convert the hidden struct to a list.
patches: [for x in _patches {x}]
}
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > components/httpbin/httpbin.cue

View File

@@ -0,0 +1,2 @@
rendered httpbin in 197.030208ms
rendered platform in 197.416416ms

View File

@@ -0,0 +1,16 @@
package holos
import "encoding/yaml"
// Mix in a Kustomize patch to the configuration.
Kustomize: KustomizeConfig: Kustomization: _patches: {
probe: {
target: kind: "Service"
target: name: "httpbin"
patch: yaml.Marshal([{
op: "add"
path: "/metadata/annotations/prometheus.io~1probe"
value: "true"
}])
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > components/httpbin/patches.cue

View File

@@ -0,0 +1,36 @@
# https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
template:
spec:
containers:
- name: httpbin
image: mccutchen/go-httpbin
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /status/200
port: http
readinessProbe:
httpGet:
path: /status/200
port: http
resources: {}
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
appProtocol: http

View File

@@ -0,0 +1 @@
cat <<EOF > components/httpbin/httpbin.yaml

View File

@@ -0,0 +1,2 @@
rendered httpbin in 132.00525ms
rendered platform in 132.124042ms

View File

@@ -0,0 +1,3 @@
mkdir holos-kustomize-tutorial
cd holos-kustomize-tutorial
holos init platform v1alpha5

View File

@@ -0,0 +1 @@
mkdir -p components/httpbin

View File

@@ -0,0 +1,8 @@
package holos
Platform: Components: {
httpbin: {
name: "httpbin"
path: "components/httpbin"
}
}

View File

@@ -0,0 +1 @@
cat <<EOF > platform/httpbin.cue

View File

@@ -0,0 +1,2 @@
rendered httpbin in 175.057083ms
rendered platform in 175.145292ms

View File

@@ -0,0 +1 @@
holos render platform

View File

@@ -0,0 +1,4 @@
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"

View File

@@ -7,6 +7,7 @@ sidebar_position: 50
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
# CUE
@@ -25,92 +26,69 @@ Key concepts:
## The Code
### Holos Version
Ensure you have a current version of `holos` installed. This document was
tested with the following version.
import HolosVersionCommand from '!!raw-loader!./_cue/script-01-holos-version/command.sh';
import HolosVersionOutput from '!!raw-loader!./_cue/script-01-holos-version/output.txt';
<CodeBlock language="bash">{HolosVersionCommand}</CodeBlock>
<CodeBlock language="txt">{HolosVersionOutput}</CodeBlock>
### Generating the Structure
Use `holos` to generate a minimal platform directory structure. First, create
and navigate into a blank directory. Then, use the `holos generate platform`
command to generate a minimal platform.
```shell
mkdir holos-cue-tutorial && cd holos-cue-tutorial
holos init platform v1alpha5
```
import MkdirAndInit from '!!raw-loader!./_cue/script-02-cue/mkdir-and-init.sh';
<CodeBlock language="bash">{MkdirAndInit}</CodeBlock>
### Creating the Component
Create the directory for the `podinfo` component. Create an empty file, then add
the following CUE configuration to it.
```bash
mkdir -p components/podinfo
```
```bash
cat <<EOF > components/podinfo/podinfo.cue
```
```cue showLineNumbers
package holos
import MkdirComponents from '!!raw-loader!./_cue/script-02-cue/mkdir-components.sh';
import PodinfoHeader from '!!raw-loader!./_cue/script-02-cue/podinfo-component-header.sh';
import PodinfoBody from '!!raw-loader!./_cue/script-02-cue/podinfo-component-body.cue';
import EofTrailer from '!!raw-loader!./_cue/script-02-cue/eof-trailer.sh';
// export the component build plan to holos
holos: Component.BuildPlan
// Component is a Helm chart
Component: #Helm & {
Name: "podinfo"
Namespace: "default"
// Add metadata.namespace to all resources with kustomize.
KustomizeConfig: Kustomization: namespace: Namespace
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{PodinfoHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{PodinfoBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
Register the component with the platform.
```bash
cat <<EOF > platform/podinfo.cue
```
```cue showLineNumbers
package holos
import RegisterHeader from '!!raw-loader!./_cue/script-02-cue/register-components-header.sh';
import RegisterBody from '!!raw-loader!./_cue/script-02-cue/register-components-body.cue';
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
}
```
```bash
EOF
```
<CodeBlock language="bash">{RegisterHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{RegisterBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
Render the platform.
import RenderCommand from '!!raw-loader!./_cue/script-02-cue/render.sh';
import RegisterOutput from '!!raw-loader!./_cue/script-02-cue/register-components-output.txt';
<Tabs groupId="tutorial-hello-render-manifests">
<TabItem value="command" label="Command">
```bash
holos render platform
```
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```
cached podinfo 6.6.2
rendered podinfo in 1.938665041s
rendered platform in 1.938759417s
```
<CodeBlock language="txt">{RegisterOutput}</CodeBlock>
</TabItem>
</Tabs>
Add and commit the initial configuration.
```bash
git init . && git add . && git commit -m initial
```
import GitInit from '!!raw-loader!./_cue/script-02-cue/git-init.sh';
<CodeBlock language="bash">{GitInit}</CodeBlock>
### Mixing in Resources
@@ -120,39 +98,12 @@ component kind. This field is a convenient wrapper around the core [BuildPlan]
Create the mixins.cue file.
```bash
cat <<EOF > components/podinfo/mixins.cue
```
```cue showLineNumbers
package holos
import MixinHeader from '!!raw-loader!./_cue/script-02-cue/mixin-component-header.sh';
import MixinBody from '!!raw-loader!./_cue/script-02-cue/mixin-component-body.cue';
// Component fields are unified with podinfo.cue
Component: {
// Concrete values are defined in podinfo.cue
Name: string
Namespace: string
// Resources represents mix-in resources organized as a struct.
Resources: ExternalSecret: (Name): {
// Name is consistent with the component name.
metadata: name: Name
// Namespace is consistent with the component namespace.
metadata: namespace: Namespace
spec: {
// Ensure the target secret name is consistent.
target: name: metadata.name
// Ensure the name in the SecretStore is consistent.
dataFrom: [{extract: {key: metadata.name}}]
refreshInterval: "30s"
secretStoreRef: kind: "SecretStore"
secretStoreRef: name: "default"
}
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{MixinHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{MixinBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
:::important
Holos uses CUE to validate mixed in resources against a schema. The `Resources`
@@ -169,32 +120,15 @@ tutorial.
To import your own custom resource definitions, use [Timoni]. We imported the
ExternalSecret CRDs embedded in `holos` using the following command.
import ImportCRDs from '!!raw-loader!./_cue/script-02-cue/import-crds.sh';
import ImportOutput from '!!raw-loader!./_cue/script-02-cue/timoni-vendor.txt';
<Tabs groupId="35B1A1A1-D7DF-4D27-A575-28556E182096">
<TabItem value="command" label="Command">
```bash
timoni mod vendor crds -f https://raw.githubusercontent.com/external-secrets/external-secrets/v0.10.5/deploy/crds/bundle.yaml
```
<CodeBlock language="bash">{ImportCRDs}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```txt
2:22PM INF schemas vendored: external-secrets.io/clusterexternalsecret/v1beta1
2:22PM INF schemas vendored: external-secrets.io/clustersecretstore/v1alpha1
2:22PM INF schemas vendored: external-secrets.io/clustersecretstore/v1beta1
2:22PM INF schemas vendored: external-secrets.io/externalsecret/v1alpha1
2:22PM INF schemas vendored: external-secrets.io/externalsecret/v1beta1
2:22PM INF schemas vendored: external-secrets.io/pushsecret/v1alpha1
2:22PM INF schemas vendored: external-secrets.io/secretstore/v1alpha1
2:22PM INF schemas vendored: external-secrets.io/secretstore/v1beta1
2:22PM INF schemas vendored: generators.external-secrets.io/acraccesstoken/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/ecrauthorizationtoken/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/fake/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/gcraccesstoken/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/githubaccesstoken/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/password/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/uuid/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/vaultdynamicsecret/v1alpha1
2:22PM INF schemas vendored: generators.external-secrets.io/webhook/v1alpha1
```
<CodeBlock language="txt">{ImportOutput}</CodeBlock>
</TabItem>
</Tabs>
@@ -212,42 +146,15 @@ existing [resources.cue] file.
Render the platform with the `ExternalSecret` mixed into the podinfo component.
```shell
holos render platform
```
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
Take a look at the diff to see the mixed in `ExternalSecret`.
```shell
git diff deploy
```
import GitDiff from '!!raw-loader!./_cue/script-02-cue/git-diff.sh';
import DiffOutput from '!!raw-loader!./_cue/script-02-cue/git.diff';
```diff
diff --git a/deploy/components/podinfo/podinfo.gen.yaml b/deploy/components/podinfo/podinfo.gen.yaml
index 6e4aec0..f79e9d0 100644
--- a/deploy/components/podinfo/podinfo.gen.yaml
+++ b/deploy/components/podinfo/podinfo.gen.yaml
@@ -112,3 +112,19 @@ spec:
volumes:
- emptyDir: {}
name: data
+---
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: podinfo
+ namespace: default
+spec:
+ dataFrom:
+ - extract:
+ key: podinfo
+ refreshInterval: 30s
+ secretStoreRef:
+ kind: SecretStore
+ name: default
+ target:
+ name: podinfo
```
<CodeBlock language="bash">{GitDiff}</CodeBlock>
<CodeBlock language="diff">{DiffOutput}</CodeBlock>
We saw how to mix in resources using the `Resources` field of the
[ComponentConfig]. This approach works for every kind of component in Holos,

View File

@@ -0,0 +1,19 @@
package main
import (
"path/filepath"
"testing"
)
// Run these with go test -v to see the verbose names
func TestCue(t *testing.T) {
t.Run("TestCue", func(t *testing.T) {
// Get an ordered list of test script files.
dir := "_cue"
for _, file := range sortedTestScripts(t, filepath.Join(dir, "examples")) {
t.Run("examples", func(t *testing.T) {
runOneScript(t, dir, file)
})
}
})
}

View File

@@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem';
import RenderingOverview from '@site/src/diagrams/rendering-overview.mdx';
import PlatformSequence from '@site/src/diagrams/render-platform-sequence.mdx';
import ComponentSequence from '@site/src/diagrams/render-component-sequence.mdx';
import CodeBlock from '@theme/CodeBlock';
# Hello Holos
@@ -21,49 +22,37 @@ This introduces the core concept of wrapping Helm charts as Holos Components.
## Implementation
### Holos Version
Ensure you have a current version of `holos` installed. This document was
tested with the following version.
import HolosVersionCommand from '!!raw-loader!./_hello-holos/script-01-holos-version/command.sh';
import HolosVersionOutput from '!!raw-loader!./_hello-holos/script-01-holos-version/output.txt';
<CodeBlock language="bash">{HolosVersionCommand}</CodeBlock>
<CodeBlock language="txt">{HolosVersionOutput}</CodeBlock>
### Initialize Platform Structure
Create and initialize a minimal platform:
```shell
mkdir holos-tutorial && cd holos-tutorial
holos init platform v1alpha5
```
import MkdirAndInit from '!!raw-loader!./_hello-holos/script-02-hello-holos/mkdir-and-init.sh';
import TreeOutput from '!!raw-loader!./_hello-holos/script-02-hello-holos/tree.txt';
The resulting directory structure:
<CodeBlock language="bash">{MkdirAndInit}</CodeBlock>
For reference, the directory structure you will attain by the end of this tutorial
is listed below (NOTE: we have omitted the `cue.mod` directory for brevity):
<Tabs groupId="80D04C6A-BC83-44D0-95CC-CE01B439B159">
<TabItem value="tree" label="Tree">
```text showLineNumbers
holos-tutorial/
├── components/
│   └── podinfo/
│   └── podinfo.cue
├── cue.mod/
├── platform/
│   ├── platform.gen.cue
│   └── podinfo.cue
├── resources.cue
├── schema.cue
└── tags.cue
```
<CodeBlock language="txt" showLineNumbers>{TreeOutput}</CodeBlock>
</TabItem>
<TabItem value="details" label="Details">
<div style={{display: "flex"}}>
<div>
```text showLineNumbers
holos-tutorial/
├── components/
│   └── podinfo/
│   └── podinfo.cue
├── cue.mod/
├── platform/
│   ├── platform.gen.cue
│   └── podinfo.cue
├── resources.cue
├── schema.cue
└── tags.cue
```
<CodeBlock language="txt" showLineNumbers>{TreeOutput}</CodeBlock>
</div>
<div>
- **Line 1** The platform root is the `holos-tutorial` directory we created.
@@ -72,22 +61,24 @@ anywhere.
- **Line 3** A component is a collection of `*.cue` files at a path.
- **Line 4** We'll create this file and configure the podinfo helm chart in the
next section.
- **Line 5** The CUE module directory. Schema definitions for Kubernetes and
Holos resources reside within the `cue.mod` directory.
- **Line 6** The platform directory is the **main entrypoint** for the `holos
- **Line 5** The `vendor` directory contains a cached copy of the Helm chart that
was fetched for the component.
- **Line 6** Rendered manifests are placed within the `deploy` directory following
the structure of the `components/` directory.
- **Line 9** The platform directory is the **main entrypoint** for the `holos
render platform` command.
- **Line 7** `platform.gen.cue` is initialized by `holos init platform` and
- **Line 10** `platform.gen.cue` is initialized by `holos init platform` and
contains the Platform spec.
- **Line 8** `podinfo.cue` integrates podinfo with the platform by adding the
- **Line 11** `podinfo.cue` integrates podinfo with the platform by adding the
component to the platform spec. We'll add ths file after the next section.
- **Line 9** `resources.cue` Defines the Kubernetes resources available to
- **Line 13** `resources.cue` Defines the Kubernetes resources available to
manage in CUE.
- **Line 10** `schema.cue` Defines the configuration common to all component
- **Line 14** `schema.cue` Defines the configuration common to all component
kinds.
- **Line 11** `tags.cue` Defines where component parameter values are injected
- **Line 15** `tags.cue` Defines where component parameter values are injected
into the overall platform configuration. We don't need to be concerned with
this file until we cover component parameters.
- **Lines 9-11** Initialized by `holos init platform`, user editable after
- **Lines 9-15** Initialized by `holos init platform`, user editable after
initialization.
</div>
</div>
@@ -98,40 +89,15 @@ initialization.
Configure the `podinfo` component:
```bash
mkdir -p components/podinfo
```
```bash
cat <<EOF > components/podinfo/podinfo.cue
```
```cue showLineNumbers
package holos
import MkdirComponents from '!!raw-loader!./_hello-holos/script-02-hello-holos/mkdir-components.sh';
import PodinfoHeader from '!!raw-loader!./_hello-holos/script-02-hello-holos/podinfo-component-header.sh';
import PodinfoBody from '!!raw-loader!./_hello-holos/script-02-hello-holos/podinfo-component-body.cue';
import EofTrailer from '!!raw-loader!./_hello-holos/script-02-hello-holos/eof-trailer.sh';
// Produce a helm chart build plan.
holos: HelmChart.BuildPlan
HelmChart: #Helm & {
Name: "podinfo"
Chart: {
version: "6.6.2"
repository: {
name: "podinfo"
url: "https://stefanprodan.github.io/podinfo"
}
}
// Holos marshals Values into values.yaml for Helm.
Values: {
// message is a string with a default value. @tag indicates a value may
// be injected from the platform spec component parameters.
ui: {
message: string | *"Hello World" @tag(greeting, type=string)
}
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{MkdirComponents}</CodeBlock>
<CodeBlock language="bash">{PodinfoHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{PodinfoBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
:::important
Like Go packages, CUE loads all `*.cue` files in the component directory to
@@ -148,22 +114,12 @@ root-level `schema.cue`.
Register the `podinfo` component in `platform/podinfo.cue`:
```bash
cat <<EOF > platform/podinfo.cue
```
```cue showLineNumbers
package holos
import RegisterPodinfoHeader from '!!raw-loader!./_hello-holos/script-02-hello-holos/register-podinfo-header.sh';
import RegisterPodinfoBody from '!!raw-loader!./_hello-holos/script-02-hello-holos/register-podinfo-body.cue';
Platform: Components: podinfo: {
name: "podinfo"
path: "components/podinfo"
// Inject a value into the component.
parameters: greeting: "Hello Holos!"
}
```
```bash
EOF
```
<CodeBlock language="bash">{RegisterPodinfoHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{RegisterPodinfoBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
:::tip
Parameter names are unrestricted, except for the reserved `holos_` prefix.
@@ -173,161 +129,42 @@ Parameter names are unrestricted, except for the reserved `holos_` prefix.
Render the `podinfo` configuration:
import RenderCommand from '!!raw-loader!./_hello-holos/script-02-hello-holos/render.sh';
import RegisterComponentsOutput from '!!raw-loader!./_hello-holos/script-02-hello-holos/register-components-output.txt';
<Tabs groupId="E150C802-7162-4FBF-82A7-77D9ADAEE847">
<TabItem value="command" label="Command">
```bash
holos render platform
```
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```
cached podinfo 6.6.2
rendered podinfo in 1.938665041s
rendered platform in 1.938759417s
```
<CodeBlock language="txt">{RegisterComponentsOutput}</CodeBlock>
</TabItem>
</Tabs>
Holos executes `helm template` with locally cached charts to generate:
```txt
deploy/components/podinfo/podinfo.gen.yaml
```
import PodinfoRenderedPath from '!!raw-loader!./_hello-holos/script-02-hello-holos/podinfo-rendered-path.sh';
import RenderedService from '!!raw-loader!./_hello-holos/script-02-hello-holos/rendered-service.yaml';
import RenderedDeployment from '!!raw-loader!./_hello-holos/script-02-hello-holos/rendered-deployment.yaml';
<CodeBlock language="txt">{PodinfoRenderedPath}</CodeBlock>
<Tabs groupId="0E9C231D-D0E8-410A-A4A0-601842A086A6">
<TabItem value="service" label="Service">
```yaml showLineNumbers
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo
app.kubernetes.io/version: 6.6.2
helm.sh/chart: podinfo-6.6.2
name: podinfo
spec:
ports:
- name: http
port: 9898
protocol: TCP
targetPort: http
- name: grpc
port: 9999
protocol: TCP
targetPort: grpc
selector:
app.kubernetes.io/name: podinfo
type: ClusterIP
```
<CodeBlock language="yaml" showLineNumbers>{RenderedService}</CodeBlock>
</TabItem>
<TabItem value="deployment" label="Deployment">
```yaml showLineNumbers
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: podinfo
app.kubernetes.io/version: 6.6.2
helm.sh/chart: podinfo-6.6.2
name: podinfo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: podinfo
strategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
annotations:
prometheus.io/port: "9898"
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/name: podinfo
spec:
containers:
- command:
- ./podinfo
- --port=9898
- --cert-path=/data/cert
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false
env:
- name: PODINFO_UI_MESSAGE
value: Hello Holos!
- name: PODINFO_UI_COLOR
value: '#34577c'
image: ghcr.io/stefanprodan/podinfo:6.6.2
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/healthz
failureThreshold: 3
initialDelaySeconds: 1
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
name: podinfo
ports:
- containerPort: 9898
name: http
protocol: TCP
- containerPort: 9797
name: http-metrics
protocol: TCP
- containerPort: 9999
name: grpc
protocol: TCP
readinessProbe:
exec:
command:
- podcli
- check
- http
- localhost:9898/readyz
failureThreshold: 3
initialDelaySeconds: 1
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
resources:
limits: null
requests:
cpu: 1m
memory: 16Mi
volumeMounts:
- mountPath: /data
name: data
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: data
```
<CodeBlock language="yaml" showLineNumbers>{RenderedDeployment}</CodeBlock>
</TabItem>
</Tabs>
Holos renders the component with the greeting injected from the platform spec.
```shell
grep -B2 Hello deploy/components/podinfo/podinfo.gen.yaml
```
```yaml
env:
- name: PODINFO_UI_MESSAGE
value: Hello Holos!
```
import GrepForMessage from '!!raw-loader!./_hello-holos/script-02-hello-holos/grep-for-message.sh';
import GreppedOutput from '!!raw-loader!./_hello-holos/script-02-hello-holos/grepped-output.txt';
<CodeBlock language="bash">{GrepForMessage}</CodeBlock>
<CodeBlock language="yaml">{GreppedOutput}</CodeBlock>
## Breaking it down

View File

@@ -0,0 +1,19 @@
package main
import (
"path/filepath"
"testing"
)
// Run these with go test -v to see the verbose names
func TestHelloHolos(t *testing.T) {
t.Run("TestHelloHolos", func(t *testing.T) {
// Get an ordered list of test script files.
dir := "_hello-holos"
for _, file := range sortedTestScripts(t, filepath.Join(dir, "examples")) {
t.Run("examples", func(t *testing.T) {
runOneScript(t, dir, file)
})
}
})
}

View File

@@ -7,6 +7,7 @@ sidebar_position: 45
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
# Kustomize
@@ -24,6 +25,17 @@ covered in the [Helm Values] tutorial.
## The Code
### Holos Version
Ensure you have a current version of `holos` installed. This document was
tested with the following version.
import HolosVersionCommand from '!!raw-loader!./_kustomize/script-01-holos-version/command.sh';
import HolosVersionOutput from '!!raw-loader!./_kustomize/script-01-holos-version/output.txt';
<CodeBlock language="bash">{HolosVersionCommand}</CodeBlock>
<CodeBlock language="txt">{HolosVersionOutput}</CodeBlock>
### Generating the structure
<Tabs>
@@ -39,18 +51,14 @@ Use `holos` to generate a minimal platform directory structure. First, create
and navigate into a blank directory. Then, run the `holos init platform`
command.
```shell
mkdir holos-kustomize-tutorial
cd holos-kustomize-tutorial
holos init platform v1alpha5
```
import MkdirAndInit from '!!raw-loader!./_kustomize/script-02-kustomize/mkdir-and-init.sh';
import GitInit from '!!raw-loader!./_kustomize/script-02-kustomize/git-init.sh';
<CodeBlock language="bash">{MkdirAndInit}</CodeBlock>
Make a commit to track changes.
```bash
git init . && git add . && git commit -m initial
```
<CodeBlock language="bash">{GitInit}</CodeBlock>
</TabItem>
</Tabs>
@@ -59,97 +67,26 @@ git init . && git add . && git commit -m initial
Create the `httpbin` component directory, and add the `httpbin.cue` and
`httpbin.yaml` files to it for configuration and setup.
import MkdirComponent from '!!raw-loader!./_kustomize/script-02-kustomize/mkdir-component.sh';
import HttpbinComponentHeader from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-component-header.sh';
import HttpbinComponentBody from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-component-body.cue';
import EofTrailer from '!!raw-loader!./_kustomize/script-02-kustomize/eof-trailer.sh';
import HttpbinYamlHeader from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-yaml-header.sh';
import HttpbinYamlBody from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-yaml-body.yaml';
<Tabs groupId="800C3AE7-E7F8-4AFC-ABF1-6AFECD945958">
<TabItem value="setup" label="Setup">
```bash
mkdir -p components/httpbin
```
<CodeBlock language="bash">{MkdirComponent}</CodeBlock>
</TabItem>
<TabItem value="components/httpbin/httpbin.cue" label="httpbin.cue">
```bash
cat <<EOF > components/httpbin/httpbin.cue
```
```cue showLineNumbers
package holos
// Produce a Kustomize BuildPlan for Holos
holos: Kustomize.BuildPlan
// https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/README.md
Kustomize: #Kustomize & {
KustomizeConfig: {
// Files tells Holos to copy the file from the component path to the
// temporary directory Holos uses for BuildPlan execution.
Files: {
"httpbin.yaml": _
}
CommonLabels: {
"app.kubernetes.io/name": "httpbin"
}
// Kustomization represents a kustomization.yaml file in CUE. Holos
// marshals this field into a `kustomization.yaml` while processing a
// BuildPlan. See
// https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/
Kustomization: {
images: [{name: "mccutchen/go-httpbin"}]
// Use a hidden field to compose patches easily with a struct. Hidden
// fields are not included in exported structures.
_patches: {}
// Convert the hidden struct to a list.
patches: [for x in _patches {x}]
}
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{HttpbinComponentHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{HttpbinComponentBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
</TabItem>
<TabItem value="components/httpbin/httpbin.yaml" label="httpbin.yaml">
```bash
cat <<EOF > components/httpbin/httpbin.yaml
```
```yaml showLineNumbers
# https://github.com/mccutchen/go-httpbin/blob/v2.15.0/kustomize/resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
template:
spec:
containers:
- name: httpbin
image: mccutchen/go-httpbin
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /status/200
port: http
readinessProbe:
httpGet:
path: /status/200
port: http
resources: {}
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
appProtocol: http
```
```bash
EOF
```
<CodeBlock language="bash">{HttpbinYamlHeader}</CodeBlock>
<CodeBlock language="yaml" showLineNumbers>{HttpbinYamlBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
</TabItem>
</Tabs>
@@ -161,56 +98,38 @@ Holos knows the `httpbin.yaml` file is part of the BuildPlan because of the
Register `httpbin` with the platform by adding the following file to the
platform directory.
```bash
cat <<EOF > platform/httpbin.cue
```
```cue showLineNumbers
package holos
import RegisterComponentHeader from '!!raw-loader!./_kustomize/script-02-kustomize/register-component-header.sh';
import RegisterComponentBody from '!!raw-loader!./_kustomize/script-02-kustomize/register-component-body.cue';
Platform: Components: {
httpbin: {
name: "httpbin"
path: "components/httpbin"
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{RegisterComponentHeader}</CodeBlock>
<CodeBlock language="cue">{RegisterComponentBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
Render the platform.
import RenderCommand from '!!raw-loader!./_kustomize/script-02-kustomize/render.sh';
import RegisterComponentOutput from '!!raw-loader!./_kustomize/script-02-kustomize/register-component-output.txt';
<Tabs groupId="B120D5D1-0EAB-41E0-AD21-15526EBDD53D">
<TabItem value="command" label="Command">
```bash
holos render platform
```
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```txt
rendered httpbin in 707.554666ms
rendered platform in 707.9845ms
```
<CodeBlock language="txt">{RegisterComponentOutput}</CodeBlock>
</TabItem>
</Tabs>
Commit the results.
import GitCommitComponent from '!!raw-loader!./_kustomize/script-02-kustomize/git-commit-component.sh';
import GitCommitComponentOutput from '!!raw-loader!./_kustomize/script-02-kustomize/git-commit-component-output.txt';
<Tabs groupId="446CC550-A634-45C0-BEC7-992E5C56D4FA">
<TabItem value="command" label="Command">
```bash
git add . && git commit -m 'add httpbin'
```
<CodeBlock language="bash">{GitCommitComponent}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```txt
[main c05f9ef] add httpbin
4 files changed, 118 insertions(+)
create mode 100644 components/httpbin/httpbin.cue
create mode 100644 components/httpbin/httpbin.yaml
create mode 100644 deploy/components/httpbin/httpbin.gen.yaml
create mode 100644 platform/httpbin.cue
```
<CodeBlock language="txt">{GitCommitComponentOutput}</CodeBlock>
</TabItem>
</Tabs>
@@ -220,55 +139,15 @@ We can see the [BuildPlan] exported to `holos` by the `holos:
Kustomize.BuildPlan` line in `httpbin.cue`. Holos processes this build plan to
produce the fully rendered manifests.
import CueExport from '!!raw-loader!./_kustomize/script-02-kustomize/cue-export.sh';
import BuildplanOutput from '!!raw-loader!./_kustomize/script-02-kustomize/buildplan-output.cue';
<Tabs groupId="DD697D65-5BEC-4B92-BB33-59BE4FEC112F">
<TabItem value="command" label="Command">
```bash
holos cue export --expression holos --out=yaml ./components/httpbin
```
<CodeBlock language="bash">{CueExport}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```yaml showLineNumbers
kind: BuildPlan
apiVersion: v1alpha5
metadata:
name: no-name
spec:
artifacts:
- artifact: components/no-name/no-name.gen.yaml
generators:
- kind: Resources
output: resources.gen.yaml
resources: {}
- kind: File
output: httpbin.yaml
file:
source: httpbin.yaml
transformers:
- kind: Kustomize
inputs:
- resources.gen.yaml
- httpbin.yaml
output: components/no-name/no-name.gen.yaml
kustomize:
kustomization:
labels:
- includeSelectors: false
pairs:
app.kubernetes.io/name: httpbin
patches: []
images:
- name: mccutchen/go-httpbin
resources:
- resources.gen.yaml
- httpbin.yaml
kind: Kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
source:
component:
name: no-name
path: no-path
parameters: {}
```
<CodeBlock language="yaml">{BuildplanOutput}</CodeBlock>
</TabItem>
</Tabs>
@@ -291,30 +170,12 @@ need to edit any YAML files manually.
Add a new `patches.cue` file to the `httpbin` component with the following
content.
```bash
cat <<EOF > components/httpbin/patches.cue
```
```cue showLineNumbers
package holos
import HttpbinPatchHeader from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-patch-header.sh';
import HttpbinPatchBody from '!!raw-loader!./_kustomize/script-02-kustomize/httpbin-patch-body.cue';
import "encoding/yaml"
// Mix in a Kustomize patch to the configuration.
Kustomize: KustomizeConfig: Kustomization: _patches: {
probe: {
target: kind: "Service"
target: name: "httpbin"
patch: yaml.Marshal([{
op: "add"
path: "/metadata/annotations/prometheus.io~1probe"
value: "true"
}])
}
}
```
```bash
EOF
```
<CodeBlock language="bash">{HttpbinPatchHeader}</CodeBlock>
<CodeBlock language="bash" showLineNumbers>{HttpbinPatchBody}</CodeBlock>
<CodeBlock language="bash">{EofTrailer}</CodeBlock>
:::note
We use a hidden `_patches` field to easily unify data into a struct, then
@@ -325,62 +186,43 @@ convert the struct into a list for export.
Render the platform to see the result of the kustomization patch.
import KustomizePatchRenderOutput from '!!raw-loader!./_kustomize/script-02-kustomize/kustomize-patch-render-output.txt';
<Tabs groupId="5D1812DD-8E7B-4F97-B349-275214F38B6E">
<TabItem value="command" label="Command">
```bash
holos render platform
```
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```txt
rendered httpbin in 197.030208ms
rendered platform in 197.416416ms
```
<CodeBlock language="txt">{KustomizePatchRenderOutput}</CodeBlock>
</TabItem>
</Tabs>
Holos is configuring Kustomize to patch the plain `httpbin.yaml` file with the
annotation.
import GitDiff from '!!raw-loader!./_kustomize/script-02-kustomize/git-diff.sh';
import GitDiffOutput from '!!raw-loader!./_kustomize/script-02-kustomize/git.diff';
<Tabs groupId="3D80279E-8EDE-4B3E-9269-50F5D1C1CA42">
<TabItem value="command" label="Command">
```bash
git diff
```
<CodeBlock language="bash">{GitDiff}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```diff
diff --git a/deploy/components/httpbin/httpbin.gen.yaml b/deploy/components/httpbin/httpbin.gen.yaml
index 298b9a8..a16bd1a 100644
--- a/deploy/components/httpbin/httpbin.gen.yaml
+++ b/deploy/components/httpbin/httpbin.gen.yaml
@@ -1,6 +1,8 @@
apiVersion: v1
kind: Service
metadata:
+ annotations:
+ prometheus.io/probe: "true"
labels:
app.kubernetes.io/name: httpbin
name: httpbin
```
<CodeBlock language="diff">{GitDiffOutput}</CodeBlock>
</TabItem>
</Tabs>
Add and commit the final changes.
import GitCommitFinal from '!!raw-loader!./_kustomize/script-02-kustomize/git-commit-final.sh';
import GitCommitFinalOutput from '!!raw-loader!./_kustomize/script-02-kustomize/git-commit-final-output.txt';
<Tabs groupId="54C335C8-B382-4277-AE87-0D6556921955">
<TabItem value="command" label="Command">
```bash
git add . && git commit -m 'annotate httpbin for prometheus probes'
```
<CodeBlock language="bash">{GitCommitFinal}</CodeBlock>
</TabItem>
<TabItem value="output" label="Output">
```txt
[main 6eeeadb] annotate httpbin for prometheus probes
2 files changed, 3 insertions(+), 1 deletion(-)
```
<CodeBlock language="txt">{GitCommitFinalOutput}</CodeBlock>
</TabItem>
</Tabs>

View File

@@ -0,0 +1,19 @@
package main
import (
"path/filepath"
"testing"
)
// Run these with go test -v to see the verbose names
func TestKustomize(t *testing.T) {
t.Run("TestKustomize", func(t *testing.T) {
// Get an ordered list of test script files.
dir := "_kustomize"
for _, file := range sortedTestScripts(t, filepath.Join(dir, "examples")) {
t.Run("examples", func(t *testing.T) {
runOneScript(t, dir, file)
})
}
})
}

View File

@@ -18,3 +18,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Auto-generated doc examples
multi-sources-example/
kargo-demo/

View File

@@ -0,0 +1,627 @@
---
unlisted: true
slug: trade-in-argocd-appsets-for-rendered-manifests
title: Trade in the complexity of ArgoCD AppSets for fully rendered manifests
authors: [jeff]
tags: [holos, helm, gitops]
# image: /img/cards/validators.png
description: Migrate an ApplicationSet to the rendered manifest pattern with Holos, removing unnecessary complexity.
keywords:
- Holos
- CUE
- Configuration
- Structure
- Kubernetes
- Hydrated
- Rendered
- Manifest
- Pattern
- Unification
- ArgoCD
- ApplicationSet
- Application
- Multi Source
- Values Hierarchy
- Rendered Manifest Pattern
- GitOps
- Complexity
---
Kubernetes has a reputation for being too complex. Complexity in software
engineering is often categorized as essential or accidental. Methods to expose
services to the internet, deploy replicas, manage secrets, configure network
connectivity, and control access are examples of essential complexity. They're
unavoidable no matter what tools we use.
Nevertheless, popular tools in the ecosystem bolster this reputation by
accidentally piling up layers of complexity unnecessarily. For example, Helm
value override hierarchies and ArgoCD ApplicationSet templates. Both tools
solve challenging, pervasive problems resulting in their widespread adoption.
Unfortunately the trade offs each one makes independently combine together
poorly. Helm and ArgoCD ApplicationSets contribute more than their fair share
of accidental complexity to the Kubernetes ecosystem.
Consider the use case of deploying different versions of the same service to
multiple environments. An ArgoCD ApplicationSet passing sets of values to one
Helm chart is a commonly recommended solution contributing three forms of
accidental complexity.
1. There are multiple layers of Go template abstractions.
2. Config values are silently written over at multiple layers in a hierarchy.
3. The intermediate and final configuration is remote and out of reach.
The rendered manifests pattern is an alternative solution leveraging Helm and
ArgoCD with less complexity. The pattern reduces complexity by collapsing
multiple layers of Go templates and brings the configuration local, within our
reach. It still relies on Helm value hierarchies when charts are reused across
environments, so it's no silver bullet, but we'll explain in follow up post how
Holos can eliminate the accidental complexity of a Helm value files hierarchy.
There is no widely agreed upon, freely available implementation of the rendered
manifest pattern. Engineering teams have to decide whether to implement the
pattern from scratch or stick with the more complex, but built-in features of
Helm and ArgoCD. These few options indicate a tooling gap in the ecosystem.
This gap pushes many organizations toward the accidental complexity of the
ApplicationSet solution.
Holos fills this gap by offering a thoughtful and complete implementation of the
rendered manifest pattern in one command line tool. This article is the first
in a series exploring how Holos solves the same use case while avoiding the
accidental complexity.
This article walks step-by-step through the process of migrating an
ApplicationSet to Holos. I'll explain why we feel the trades we made in Holos
are a net improvement. At the end of the article you'll see how you can
continue leveraging GitOps with ArgoCD and Helm while gaining the ability to see
and comprehend complex configurations clearly, with fewer layers of abstraction.
{/* truncate */}
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
## Layers of Complexity
Before diving into the step by step migration, let's explore exactly what we
mean by accidental complexity. Consider the ApplicationSet and Helm value
hierarchy described in the final recommendation of _[Using Helm Hierarchies in
Multi-Source Argo CD Applications for Promoting to Different GitOps
Environments][tfa]_. This particular ApplicationSet is a prime example because
the approach it takes is often recommended as a solution for deploying a service
to multiple clusters and environments.
Unfortunately the example provided requires us to hold _at least 7 layers of
abstraction_ in mind while considering the impact of a change.
- The ApplicationSet renders an Application template for each `config.json` file.
- Each Application `valueFiles` field introduces 5 more layers of potential overrides.
- The Helm Chart is another layer of text templates.
:::important
Consider the multiple layers of abstraction in the example provided compared
with their replacements shown in the [Goals](#goals) section.
:::
import AppSetPath from '!!raw-loader!./_migrate_appset/script-02-layers-of-complexity/appset.path';
import AppSetYAML from '!!raw-loader!./_migrate_appset/script-02-layers-of-complexity/appset.yaml';
import DeploymentPath from '!!raw-loader!./_migrate_appset/script-02-layers-of-complexity/deployment.path';
import DeploymentYAML from '!!raw-loader!./_migrate_appset/script-02-layers-of-complexity/deployment.yaml';
<Tabs groupId="layers-of-complexity">
<TabItem value="Application Template" label="Application Template">
The ApplicationSet template renders a Helm template.
<CodeBlock language="txt">{AppSetPath}</CodeBlock>
<CodeBlock language="yaml">{AppSetYAML}</CodeBlock>
</TabItem>
<TabItem value="Deployment Template" label="Deployment Template">
The Helm template renders the final Deployment configuration.
<CodeBlock language="txt">{DeploymentPath}</CodeBlock>
<CodeBlock language="yaml">{DeploymentYAML}</CodeBlock>
</TabItem>
</Tabs>
These templates would be easier to comprehend if their intermediate state and
final configuration were within reach. Unfortunately, it's stored in multiple
remote repositories and processed remotely in ArgoCD. ArgoCD is like a black
box. Intermediate state is inaccessible. The final configuration is only
accessible after it's been applied, far too late to consider what impact a
change might have.
We know the complexity is accidental because we bypass these layers when we run
`kubectl edit`. The configuration is fetched, manipulated locally, then pushed
back to the cluster without obfuscation.
We also know the complexity is real. It's too easy to make mistakes when
configuration values are silently written over many times along the way. For
example, we'll see how the very ApplicationSet we're migrating contains errors
which are difficult to fix without insight a tool like `holos` provides.
## Goals
We want to eliminate as many layers of accidental complexity as possible with as
few changes as possible. Ideally we'll be able to directly see and manipulate
the final manifests as they will be applied to the cluster. We also want the
migration to balance the conventions and idioms of Holos, Helm, and ArgoCD as
much as possible.
1. Eliminate as many layers of accidental complexity as possible.
2. Make as few changes as possible.
3. Bring intermediate state and the final configuration within our reach.
4. Use each tool idiomatically.
We'll relocate the unmodified Helm chart, config.json files, and value files to
make as few changes as possible. We'll also generate Applications for ArgoCD
identical to those generated by the ApplicationSet, but we'll do so with the
same [CUE] layer that configures everything else in Holos.
The migration achieves these goals by rendering clearly readable Application and
Deployment resources to local files for ArgoCD to sync via GitOps. Here's how
it will look.
:::important
Compare the Application and Deployment with the templates in the [Layers of
Complexity](#layers-of-complexity) section.
:::
import AppTreeCommand from '!!raw-loader!./_migrate_appset/script-05-application/tree-deploy.sh'
import AppTreeOutput from '!!raw-loader!./_migrate_appset/script-05-application/tree-deploy.txt'
import AppPath from '!!raw-loader!./_migrate_appset/script-05-application/app.path'
import AppYAML from '!!raw-loader!./_migrate_appset/script-05-application/app.yaml'
import ManifestPath from '!!raw-loader!./_migrate_appset/script-05-application/manifest.path'
import ManifestYAML from '!!raw-loader!./_migrate_appset/script-05-application/manifest.yaml'
<Tabs groupId="goal-summary">
<TabItem value="Application" label="Application">
<CodeBlock language="yaml">
{"# "+AppPath}
{AppYAML}
</CodeBlock>
</TabItem>
<TabItem value="Deployment" label="Deployment">
<CodeBlock language="yaml">
{"# "+ManifestPath}
{ManifestYAML}
</CodeBlock>
</TabItem>
<TabItem value="Tree" label="Tree">
<CodeBlock language="bash">{AppTreeCommand}</CodeBlock>
<CodeBlock language="txt">{AppTreeOutput}</CodeBlock>
</TabItem>
</Tabs>
The manifests rendered at the end of the migration achieve the goals.
1. The Application rendered by Holos is readable and complete, replacing the ApplicationSet template rendered remotely.
2. The Application is equivalent to those produced by the ApplicationSet.
3. The final configuration is within reach in local files. We'll see how Holos exposes intermediate state as we step through the migration in the next section.
4. Both ArgoCD and Helm are used idiomatically, passing the value files to render the chart for GitOps.
## Migration Steps
We'll migrate each of the three major behaviors of the ApplicationSet to Holos
to achieve the goals and complete the migration.
1. Generate an Application from a template using values provided by each `config.json` file.
2. Render `my-chart` to a manifest by providing a hierarchy of helm values determined by `config.json` values.
3. Reconcile the rendered manifest with the cluster state.
We'll start by loading the `config.json` environment data files into CUE without
modifying the original data. Then we'll manage a Holos [Platform] [Component]
for each environment. We'll wrap `my-chart` in a component definition
and pass the value hierarchy to `helm template` the same as ArgoCD does.
Finally, we'll mix an ArgoCD Application into each platform component to achieve
the same output as the ApplicationSet.
Along the way we'll see how Holos eliminates accidental complexity and makes it
easier to work with the intermediate and final configuration.
### Initial Setup
The main branch of the [multi-sources-example] is a fork of the example code
from the original article that has already been migrated. We'll roll back to
the fork point then step through each of the commits to complete the migration.
First, clone the repository.
import GitCloneCommand from '!!raw-loader!./_migrate_appset/script-01-clone/clone.sh';
import GitCloneOutput from '!!raw-loader!./_migrate_appset/script-01-clone/clone.txt';
<Tabs groupId="clone">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{GitCloneCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="txt">{GitCloneOutput}</CodeBlock>
</TabItem>
</Tabs>
Then, reset to where it was forked from upstream.
import GitResetCommand from '!!raw-loader!./_migrate_appset/script-01-clone/reset.sh';
import GitResetOutput from '!!raw-loader!./_migrate_appset/script-01-clone/reset.txt';
<Tabs groupId="reset">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{GitResetCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="txt">{GitResetOutput}</CodeBlock>
</TabItem>
</Tabs>
[Install Holos] if you haven't already. This article has been tested with version:
import HolosVersionCmd from '!!raw-loader!./_migrate_appset/script-01-clone/version.sh';
import HolosVersionTxt from '!!raw-loader!./_migrate_appset/script-01-clone/version.txt';
<Tabs groupId="version">
<TabItem value="Version" label="Version">
<CodeBlock language="txt">{HolosVersionTxt}</CodeBlock>
</TabItem>
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{HolosVersionCmd}</CodeBlock>
</TabItem>
</Tabs>
### Holos Structure
Holos is organized for use as a GitOps repository. The main folders are:
| Folder | Description |
| - | - |
| platform | Entry point for `holos render platform` which renders all manifests. |
| config | Configuration data, we'll move the `config.json` files here. |
| components | Component definitions live here. Components produce build plans for `holos` to render manifests. We'll wrap `my-chart` in a reusable component definition. |
| deploy | Fully rendered manifests are written here for ArgoCD to sync. |
| cue.mod | [CUE] type definitions and schemas reside here. |
Initialize the platform.
import HolosInit from '!!raw-loader!./_migrate_appset/script-03-holos-structure/holos-init.sh';
<CodeBlock language="bash">{"# --force is necessary when the current directory is not empty\n"+HolosInit}</CodeBlock>
Now that we've initialized the current directory as a Holos platform repository
we can move the example files from the original article into their conventional
locations in Holos.
import MoveFiles from '!!raw-loader!./_migrate_appset/script-03-holos-structure/move-files-around.sh';
<CodeBlock language="bash">{MoveFiles}</CodeBlock>
### Environment Configs
The ApplicationSet generators field iterates over 8 config.json files to
instantiate each Application from the spec.template field. We'll migrate this
to a similar mechanism in Holos by using CUE's `@embed` feature to load the same
files into one struct. We'll manage one Helm Component for each config.json
value in the struct. This struct will reside in the `config` field of the
`environments` package. Like Go, CUE supports package imports for reuse.
These `config.json` files moved to the `config/environments/` folder. The
`config` folder is the conventional place in Holos for reusable config values
like these.
:::important
Holos offers one unified layer with CUE to configure an entire platform
holistically, different from other tools like Helm and Kustomize.
:::
Here's how the environments package is defined in CUE.
import EnvironmentsPackageHeader from '!!raw-loader!./_migrate_appset/script-03-holos-structure/environments-header.sh';
import EnvironmentsPackageBody from '!!raw-loader!./_migrate_appset/script-03-holos-structure/environments.cue';
import EnvironmentsPackageTrailer from '!!raw-loader!./_migrate_appset/script-03-holos-structure/environments-trailer.sh';
<CodeBlock language="bash">{EnvironmentsPackageHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{EnvironmentsPackageBody}</CodeBlock>
<CodeBlock language="bash">{EnvironmentsPackageTrailer}</CodeBlock>
We moved the original `config.json` files without modifying them, then used
CUE's `@embed` feature to load them into the `config` struct. This structure is
accessible in CUE by importing the `environments` package, then referencing
`environments.config`.
:::tip
Holos and CUE offer fast, local query and manipulation of your configuration
data, even in intermediate states.
:::
Here's how the environments package exports to YAML. `cue export` and `cue
eval` are handy ways to query intermediate state.
import InspectEnvironmentsCommand from '!!raw-loader!./_migrate_appset/script-03-holos-structure/inspect-environments.sh';
import InspectEnvironmentsOutput from '!!raw-loader!./_migrate_appset/script-03-holos-structure/inspect-environments.txt';
<Tabs groupId="inspect-environments">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{InspectEnvironmentsCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="yaml" showLineNumbers>{InspectEnvironmentsOutput}</CodeBlock>
</TabItem>
</Tabs>
:::important
CUE populates the `config` field on the first line of output from the `config: _ @embed(...)`
lines in `environments.cue`.
:::
Holos and CUE offer multiple improvements over the ApplicationSet we're
migrating from.
1. **Validation** - Each `config.json` file is validated against the `#Config` schema we defined.
2. **Ergonomics** - Holos enables fast, local queries over one unified
configuration. The data is at hand. We don't need a controller running in a
remote cluster. We can query the intermediate config data structure, improving
our ability to troubleshoot problems the same way an interactive debugger gives
access to intermediate state.
3. **Flexibility** - We're no longer locked into ArgoCD functionality. The
`environments` package can be imported and re-used across the unified platform
configuration layer. We're able to continue processing the rendered manifests
with other tools beyond just ArgoCD.
We've successfully migrated the ApplicationSet generator field to Holos. Next,
we'll iterate over this structure to render `my-chart` for each environment.
### Rendering my-chart
The next step is to render `my-chart` into complete configuration manifests.
Holos implements the same behavior as the Application's `spec.sources.helm`
field. Both tools use Helm to render the chart. Holos is different though, it
offers a flexible way to transform and validate the output of Helm, then stops
once the manifest is written to a local file. ArgoCD doesn't persist rendered
manifests, instead it applies them directly to the cluster.
Where Holos stops is another major difference from ArgoCD and all other tools
operating as a controller in the cluster. Holos is designed for GitOps and
integration with the rest of the Kubernetes ecosystem. Holos stops after it
writes manifests to local files. This clear cut responsibility leaves ample
space for subsequent automated workflow operating on the configuration Holos
produces.
For example, consider a progressive delivery pipeline at the right side of this
diagram to incrementally roll out configuration changes. ApplicationSets with a
Helm source prevents this kind of integration. Holos with an Application Git
source enables this kind of integration.
import RenderingOverview from '@site/src/diagrams/rendering-overview.mdx';
<RenderingOverview />
ArgoCD pairs well with other ecosystem tools when it keeps to what it does best:
drift detection and reconciliation following GitOps principles. ArgoCD locks
out other tools when it renders manifests. The configuration is transient and
locked away in the cluster.
:::important
Holos renders `my-chart` to local files, one for each of the environment configs
we migrated to the `environments` package.
:::
### Platform Components
The primary entrypoint for Holos is the `platform/` directory. The `holos
render platform` command processes a [Platform] specification exported by CUE
from this directory.
Each Application produced by the ApplicationSet we're migrating maps to a
[Component] listed in the `Platform.spec.components` field. Here's how the
components are added to the Platform in CUE.
{/* 987df87 add platform components to replace ApplicationSets.spec.generators */}
import PlatformChartHeader from '!!raw-loader!./_migrate_appset/script-04-helm-component/platform-my-chart-header.sh';
import PlatformChartBody from '!!raw-loader!./_migrate_appset/script-04-helm-component/platform-my-chart.cue';
import PlatformChartTrailer from '!!raw-loader!./_migrate_appset/script-04-helm-component/platform-my-chart-trailer.sh';
<CodeBlock language="bash">{PlatformChartHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{PlatformChartBody}</CodeBlock>
<CodeBlock language="bash">{PlatformChartTrailer}</CodeBlock>
For each of the data objects defined in the `config.json` files, we define a
field in the `Platform.Components` struct. We use a struct for convenience,
it's easier to compose components into a struct than it is into an ordered list.
The Platform author schema converts this struct into the `spec.components` list.
`#MyChart` is a schema definition acting as a reusable template. For each of
the environment config files we build the component configuration from
parameters. This is an example of how we compose configuration. The
`outputBaseDir` field is composed in from the `env` field configured in the
original `config.json` files migrated to CUE.
We need to add a configuration snippet so each component accepts this parameter
and renders manifests into folders organized by environment. The use of `@tag`
with the `OutputBaseDir` field indicates the field value is provided by the
Platform spec when we run `holos render platform`.
import ComponentConfigHeader from '!!raw-loader!./_migrate_appset/script-04-helm-component/componentconfig-header.sh';
import ComponentConfigBody from '!!raw-loader!./_migrate_appset/script-04-helm-component/componentconfig.cue';
import ComponentConfigTrailer from '!!raw-loader!./_migrate_appset/script-04-helm-component/componentconfig-trailer.sh';
<CodeBlock language="bash">{ComponentConfigHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{ComponentConfigBody}</CodeBlock>
<CodeBlock language="bash">{ComponentConfigTrailer}</CodeBlock>
We can gain insight into how `holos` renders the helm charts from the
`config.json` files with the following command. CUE exports the `Platform`
specification to `holos`, which iterates over each of the listed components to
produce a `BuildPlan`.
import ShowPlatformCommand from '!!raw-loader!./_migrate_appset/script-04-helm-component/holos-show-platform.sh';
import ShowPlatformOutput from '!!raw-loader!./_migrate_appset/script-04-helm-component/holos-show-platform.txt';
<Tabs groupId="show-platform">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{ShowPlatformCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="yaml" showLineNumbers>{ShowPlatformOutput}</CodeBlock>
</TabItem>
</Tabs>
### Component Definition
The next step is to wrap `my-chart` in a Holos [Helm] component definition.
Here's how:
import ComponentHeader from '!!raw-loader!./_migrate_appset/script-04-helm-component/component-my-chart-header.sh';
import ComponentBody from '!!raw-loader!./_migrate_appset/script-04-helm-component/component-my-chart.cue';
import ComponentFooter from '!!raw-loader!./_migrate_appset/script-04-helm-component/component-my-chart-trailer.sh';
<CodeBlock language="bash">{ComponentHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{ComponentBody}</CodeBlock>
<CodeBlock language="bash">{ComponentFooter}</CodeBlock>
Note how each parameter we added in the Platform spec is reflected in the
component definition with a `@tag`. These are CUE build tags, and the mechanism
by which parameters are passed from `holos render platform` to each component.
Similar to the `config.json` files we migrated, we moved the Helm value files
without modifying them. These files are loaded into one struct in CUE using
`valueFiles: _ @embed(...)`.
Like the Platform spec, we can inspect the BuildPlans `holos` executes to render
each component to manifest files.
import ShowBuildPlansCmd from '!!raw-loader!./_migrate_appset/script-04-helm-component/show-buildplans.sh'
import ShowBuildPlansOut from '!!raw-loader!./_migrate_appset/script-04-helm-component/show-buildplans.txt'
<Tabs groupId="show-buildplans">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{ShowBuildPlansCmd}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="yaml" showLineNumbers>{ShowBuildPlansOut}</CodeBlock>
</TabItem>
</Tabs>
We can also inspect intermediate configuration like the `valueFiles` struct.
import ValueFilesCommand from '!!raw-loader!./_migrate_appset/script-04-helm-component/inspect-value-files.sh'
import ValueFilesOutput from '!!raw-loader!./_migrate_appset/script-04-helm-component/inspect-value-files.txt'
<Tabs groupId="inspect-value-files">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{ValueFilesCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="yaml" showLineNumbers>{ValueFilesOutput}</CodeBlock>
</TabItem>
</Tabs>
Render the platform to render `my-chart` for each of the configured
environments.
import RenderCommand from '!!raw-loader!./_migrate_appset/script-04-helm-component/render.sh'
import RenderOutput from '!!raw-loader!./_migrate_appset/script-04-helm-component/render.txt'
<CodeBlock language="bash">{RenderCommand}</CodeBlock>
<CodeBlock language="txt">{RenderOutput}</CodeBlock>
Holos processes the Platform spec.components field concurrently, rendering each
environment to a manifest file into the `deploy` folder. The output looks like:
import TreeCommand from '!!raw-loader!./_migrate_appset/script-04-helm-component/tree-deploy.sh'
import TreeOutput from '!!raw-loader!./_migrate_appset/script-04-helm-component/tree-deploy.txt'
<Tabs groupId="tree-deploy">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{TreeCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="txt">{TreeOutput}</CodeBlock>
</TabItem>
</Tabs>
### ArgoCD Application
At this point we're rendering `my-chart` with Holos for each environment. The
intermediate and final configuration is within our reach. The final step is to
render an Application resource for each environment like the original
ApplicationSet did.
Holos offers [ComponentConfig] for the purpose of mixing in configuration to
components. The feature is often used to pass each component through
`kustomize` to add common labels and annotations. It's also used to mix in
GitOps resources like ArgoCD Applications and Flux Kustomizations.
Here's how to add an Application for every one of the `Platform` components:
{/* d9125b8 compose argocd application resources into every component */}
import ComponentConfigGitOpsHeader from '!!raw-loader!./_migrate_appset/script-05-application/componentconfig-gitops-header.sh';
import ComponentConfigGitOpsBody from '!!raw-loader!./_migrate_appset/script-05-application/componentconfig-gitops.cue';
import ComponentConfigGitOpsTrailer from '!!raw-loader!./_migrate_appset/script-05-application/componentconfig-gitops-trailer.sh';
<CodeBlock language="bash">{ComponentConfigGitOpsHeader}</CodeBlock>
<CodeBlock language="cue" showLineNumbers>{ComponentConfigGitOpsBody}</CodeBlock>
<CodeBlock language="bash">{ComponentConfigGitOpsTrailer}</CodeBlock>
More information about how this works is available in the following pages. For
now, it's sufficient to know the ComponentConfig is something we usually set and
forget.
1. [ComponentConfig]
2. [GitOps](/docs/v1alpha5/topics/gitops/)
Now we can render the platform and see each of the Application manifest files.
They go into a `gitops` folder so it easy to apply them individually or all at
once for ArgoCD to sync the component manifests.
import AppRenderCommand from '!!raw-loader!./_migrate_appset/script-05-application/render.sh'
import AppRenderOutput from '!!raw-loader!./_migrate_appset/script-05-application/render.txt'
<CodeBlock language="bash">{AppRenderCommand}</CodeBlock>
<CodeBlock language="txt">{AppRenderOutput}</CodeBlock>
<Tabs groupId="tree-deploy-with-application">
<TabItem value="Command" label="Command">
<CodeBlock language="bash">{AppTreeCommand}</CodeBlock>
</TabItem>
<TabItem value="Output" label="Output">
<CodeBlock language="txt">{AppTreeOutput}</CodeBlock>
</TabItem>
</Tabs>
The Applications are also fully rendered.
<CodeBlock language="txt">{AppPath}</CodeBlock>
<CodeBlock language="yaml">{AppYAML}</CodeBlock>
Note how the Application resources Holos produces are easier to read and
understand than the original ApplicationSet.
1. There is no templating.
2. There is no helm source, no value hierarchy to comprehend.
We also have the fully rendered manifest clearly readable and within reach locally.
<CodeBlock language="yaml">{"# "+ManifestPath+ManifestYAML}</CodeBlock>
## Wrapping it all up
1. The manifests are fully rendered and within our reach.
2. The Application is clear and at-hand.
3. We now have a unified platform configuration layer.
## Next Steps
- Part 2 - Why are there 8 config.json files but only 7 components rendered? There's a bug!
- Part 3 - Can we eliminate the layers of helm value overrides? Yes!
- Part 4 - Progressive Delivery. Maybe?
[tfa]: https://medium.com/containers-101/using-helm-hierarchies-in-multi-source-argo-cd-applications-for-promoting-to-different-gitops-133c3bc93678
[ApplicationSet]: https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml
[multi-sources-example]: https://github.com/holos-run/multi-sources-example
[Platform]: https://holos.run/docs/api/core/#Platform
[Component]: https://holos.run/docs/api/core/#Component
[ComponentConfig]: https://holos.run/docs/api/author/#ComponentConfig
[Helm]: https://holos.run/docs/api/core/#Helm
[CUE]: https://cuelang.org
[Install Holos]: https://holos.run/docs/setup/

View File

@@ -0,0 +1,37 @@
# This doc tested with holos version...
exec bash -c 'bash -euo pipefail $WORK/version.sh 2>&1'
cmp stdout version.txt
# Remove the directory if it already exists
exec rm -rf multi-sources-example
# Clone the repository
exec bash -c 'bash -euo pipefail $WORK/clone.sh 2>&1'
cmp stdout clone.txt
# Get the git commit
cd multi-sources-example
exec git rev-parse --verify origin/HEAD
cp stdout $WORK/git.commit
# Reset to TFA 4-final recommendation
exec bash -c 'bash -euo pipefail $WORK/reset.sh 2>&1'
cmp stdout $WORK/reset.txt
# Set the committer
exec git config user.email go-test@example.com
exec git config user.name 'go test'
-- version.sh --
holos --version
-- version.txt --
0.103.0
-- clone.sh --
git clone https://github.com/holos-run/multi-sources-example.git
cd multi-sources-example
-- clone.txt --
Cloning into 'multi-sources-example'...
-- reset.sh --
git branch -f work start
git checkout work
-- reset.txt --
Switched to branch 'work'

View File

@@ -0,0 +1,25 @@
# Work in the root of the example repo
cd ../script-01-clone/multi-sources-example
# Reset to where 01-clone left us.
exec git clean -fdx
exec git reset --hard start
# Consider the ApplicationSet from the final recommendation...
exec bash -c 'cat $(<$WORK/appset.path)'
cp stdout $WORK/appset.yaml
# The Deployment
exec bash -c 'cat $(<$WORK/deployment.path)'
cp stdout $WORK/deployment.yaml
# The Service
exec bash -c 'cat $(<$WORK/service.path)'
cp stdout $WORK/service.yaml
-- appset.path --
appsets/4-final/all-my-envs-appset-with-version.yaml
-- service.path --
my-chart/templates/service.yaml
-- deployment.path --
my-chart/templates/deployment.yaml

View File

@@ -0,0 +1,83 @@
# Work in the root of the example repo
cd ../script-01-clone/multi-sources-example
# 7ce4feb initialize the platform
exec bash -c 'bash -euo pipefail $WORK/holos-init.sh 2>&1'
# f7102d6 reorganize to conventional holos layout
exec bash -c 'bash -euo pipefail $WORK/move-files-around.sh 2>&1'
# 49183ca git mv appsets/4-final/env-config config/environments
# Note, the v0.3.0 tag contains the environments.cue prior to being updated to
# fix the duplicated config.json file. Commit 1a73e77b fixes this issue as a
# later migration step.
exec cat $WORK/environments-header.sh $WORK/environments.cue $WORK/environments-trailer.sh
stdin stdout
exec bash -xeuo pipefail
# Inspect the structure
exec bash -c 'bash -euo pipefail $WORK/inspect-environments.sh 2>&1'
cp stdout $WORK/inspect-environments.txt
-- holos-init.sh --
holos init platform v1alpha5 --force
-- move-files-around.sh --
# First, we'll move my-chart the original article vendored in
# Git to the conventional location Holos uses to vendor charts.
mkdir -p components/my-chart/vendor/0.1.0
git mv my-chart components/my-chart/vendor/0.1.0/my-chart
# Helm value files move into the directory that will contain
# my-chart component definition. components/my-chart is
# conventionally called the "my-chart component directory"
git mv my-values components/my-chart/my-values
# The config.json files are moved without changing their folder structure.
# We'll package the data up into an "environments config package" for reuse.
mkdir config
git mv appsets/4-final/env-config config/environments
# All of our components will reside in the components directory so
# the CUE files `holos init` produced may be moved to keep the
# repository root tidy.
mv *.cue components/
# The following files and directories from the original article and
# holos init are no longer relevant after the migration.
mkdir not-used
git mv appsets not-used/appsets
git mv example-apps not-used/example-apps
rm -f platform.metadata.json
# Make the commit
git add platform components cue.mod .gitignore
git commit -m 'reorganize to conventional holos layout'
-- environments-header.sh --
cat <<'EOF' > config/environments/environments.cue
-- environments.cue --
@extern(embed)
package environments
// We use cue embed functionality as an equivalent replacement for
// ApplicationSet generators.
config: _ @embed(glob=*/config.json)
config: _ @embed(glob=staging/*/config.json)
config: _ @embed(glob=prod/*/config.json)
config: _ @embed(glob=integration/*/config.json)
// With CUE we can constrain the data with a schema.
config: [FILEPATH=string]: #Config
// #Config defines the schema of each config.json file.
#Config: {
env: "qa" | "integration-gpu" | "integration-non-gpu" | "staging-us" | "staging-eu" | "prod-us" | "prod-eu"
region: "us" | "eu"
type: "prod" | "non-prod"
version: "qa" | "staging" | "prod"
chart: =~"^[0-9]+\\.[0-9]+\\.[0-9]+$"
}
-- environments-trailer.sh --
EOF
-- inspect-environments.sh --
CUE_EXPERIMENT=embed holos cue export --out=yaml \
./config/environments

View File

@@ -0,0 +1,205 @@
# Work in the root of the example repo
cd ../script-01-clone/multi-sources-example
env HOME=$WORK/.tmp
chmod 0755 $WORK/update.sh
# 987df87 add platform components to replace ApplicationSets.spec.generators
exec cat $WORK/platform-my-chart-header.sh $WORK/platform-my-chart.cue $WORK/platform-my-chart-trailer.sh
stdin stdout
exec bash -xeuo pipefail
exec diff platform/my-chart.cue $WORK/platform-my-chart.cue
# Configure where manifests are written.
exec cat $WORK/componentconfig-header.sh $WORK/componentconfig.cue $WORK/componentconfig-trailer.sh
stdin stdout
exec bash -xeuo pipefail
exec diff components/componentconfig.cue $WORK/componentconfig.cue
# Show the platform
exec bash -c 'bash -euo pipefail $WORK/holos-show-platform.sh 2>&1'
cp stdout $WORK/holos-show-platform.txt
# Component Definition
exec cat $WORK/component-my-chart-header.sh $WORK/component-my-chart.cue $WORK/component-my-chart-trailer.sh
stdin stdout
exec bash -xeuo pipefail
exec diff components/my-chart/my-chart.cue $WORK/component-my-chart.cue
# Show the BuildPlans
exec bash -c 'bash -euo pipefail $WORK/show-buildplans.sh 2>&1'
cp stdout $WORK/show-buildplans.txt
# Inspect the values
exec bash -c 'bash -euo pipefail $WORK/inspect-value-files.sh 2>&1'
cp stdout $WORK/inspect-value-files.txt
# Render the platform, capture stdout, and use update.sh to gate whether the
# output file should be updated.
exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
stdin stdout
exec $WORK/update.sh $WORK/render.txt
exec bash -c 'bash -euo pipefail $WORK/tree-deploy.sh 2>&1'
cp stdout $WORK/tree-deploy.txt
# Make a commit
exec git add .
exec git commit -m '04-helm-component.txtar'
-- tree-deploy.sh --
tree deploy
-- render.sh --
holos render platform
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- show-buildplans.sh --
holos show buildplans
-- inspect-value-files.sh --
CUE_EXPERIMENT=embed holos cue export --out=yaml \
./components/my-chart \
-e valueFiles
-- component-my-chart-header.sh --
cat <<'EOF' > components/my-chart/my-chart.cue
-- component-my-chart-trailer.sh --
EOF
-- component-my-chart.cue --
@extern(embed)
package holos
import "holos.example/config/environments"
parameters: {
environments.#Config & {
env: _ @tag(env)
region: _ @tag(region)
type: _ @tag(type)
version: _ @tag(version)
chart: _ @tag(chart)
}
}
// component represents the holos component definition, which produces a
// BuildPlan for holos to execute, rendering the manifests.
component: #Helm & {
Chart: {
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L25
version: parameters.chart
repository: {
name: "multi-sources-example"
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L23
url: "https://kostis-codefresh.github.io/multi-sources-example"
}
}
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L40
// We use kustomize to manage the namespace, similar to how the original
// article uses the ApplicationSet template to specify the namespace.
KustomizeConfig: Kustomization: namespace: parameters.env
// Migrate the Helm Hierarchy preserving the behavior of over writing values.
// Migrated from [valueFiles]. Later files win.
//
// [valueFiles]: https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L27-L32
ValueFiles: [{
name: "common-values.yaml"
values: valueFiles["my-values/common-values.yaml"]
}, {
name: "version-values.yaml"
values: valueFiles["my-values/app-version/\(parameters.version)-values.yaml"]
}, {
name: "type-values.yaml"
values: valueFiles["my-values/env-type/\(parameters.type)-values.yaml"]
}, {
name: "region-values.yaml"
values: valueFiles["my-values/regions/\(parameters.region)-values.yaml"]
}, {
name: "env-values.yaml"
values: valueFiles["my-values/envs/\(parameters.env)-values.yaml"]
}]
}
// holos represents the output for the holos command line to process. The holos
// command line processes a BuildPlan to render the helm chart component.
//
// Use the holos show buildplans command to see the BuildPlans that holos render
// platform renders.
holos: component.BuildPlan
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L27-L32
valueFiles: _ @embed(glob=my-values/*.yaml)
valueFiles: _ @embed(glob=my-values/*/*-values.yaml)
-- holos-show-platform.sh --
holos show platform
-- componentconfig-header.sh --
cat <<'EOF' > components/componentconfig.cue
-- componentconfig-trailer.sh --
EOF
-- componentconfig.cue --
package holos
#ComponentConfig: {
// Inject the output base directory from platform component parameters.
OutputBaseDir: string | *"" @tag(outputBaseDir, type=string)
}
-- platform-my-chart-header.sh --
cat <<'EOF' > platform/my-chart.cue
-- platform-my-chart-trailer.sh --
EOF
-- platform-my-chart.cue --
package holos
// Imports ./config/environments/*.cue as the environments cue package. The
// package exposes ./config/environments/**/config.json files via the
// environments.config struct
import "holos.example/config/environments"
// Manage my-chart for each of the three environments. Platform components are
// rendered by the holos render platform command.
//
// Use the following command command to inspect the Platform spec holos render
// platform processes.
//
// holos show platform
//
// CONFIG represents each migrated environments/**/config.json file.
for CONFIG in environments.config {
// Add one holos component for each config.json file to the
// Platform.spec.components list.
Platform: Components: "\(CONFIG.env)-my-chart": #MyChart & {
parameters: {
env: CONFIG.env
region: CONFIG.region
type: CONFIG.type
version: CONFIG.version
chart: CONFIG.chart
}
}
}
// #MyChart defines a re-usable way to manage my-chart across qa, staging, and
// production.
#MyChart: {
name: "my-chart"
path: "components/my-chart"
// CUE supports constraints, here we constrain environment to one of three
// possible values.
parameters: {
// Embed the config.json schema (env, region, type, version, chart fields)
environments.#Config
// Define the env field here as any value (_) so we can refer to it.
// Otherwise cue complains #MyChart.parameters.outputBaseDir: reference
// "env" not found
env: _
// Write output manifests organized by environment env in this case refers
// to the env field defined by environments.#Config
outputBaseDir: "environments/\(env)"
}
// CUE supports string substitution.
annotations: "app.holos.run/description": "my-chart \(parameters.chart) for environment \(parameters.env)"
// Selector labels
labels: env: parameters.env
}

View File

@@ -0,0 +1,141 @@
# Work in the root of the example repo
cd ../script-01-clone/multi-sources-example
chmod 0755 $WORK/update.sh
# d9125b8 compose argocd application resources into every component
exec cat $WORK/componentconfig-gitops-header.sh $WORK/componentconfig-gitops.cue $WORK/componentconfig-gitops-trailer.sh
stdin stdout
exec bash -xeuo pipefail
exec diff components/componentconfig-gitops.cue $WORK/componentconfig-gitops.cue
# Render the platform, capture stdout, and use update.sh to gate whether the
# output file should be updated.
exec bash -c 'bash -euo pipefail $WORK/render.sh 2>&1'
stdin stdout
exec $WORK/update.sh $WORK/render.txt
exec bash -c 'bash -euo pipefail $WORK/tree-deploy.sh 2>&1'
cp stdout $WORK/tree-deploy.txt
# Show an example application
exec bash -c 'cat $(<$WORK/app.path)'
stdin stdout
exec $WORK/update.sh $WORK/app.yaml
# Show an example manifest
exec bash -c 'cat $(<$WORK/manifest.path)'
stdin stdout
exec $WORK/update.sh $WORK/manifest.yaml
# Make a commit
exec git add .
exec git commit -m '05-application.txtar'
-- manifest.path --
deploy/environments/prod-us/components/my-chart/my-chart.gen.yaml
-- app.path --
deploy/gitops/prod-us-my-chart-application.gen.yaml
-- tree-deploy.sh --
tree deploy
-- render.sh --
holos render platform
-- update.sh --
#! /bin/bash
set -euo pipefail
[[ -s "$1" ]] && [[ -z "${HOLOS_UPDATE_SCRIPTS:-}" ]] && exit 0
cat > "$1"
-- componentconfig-gitops-header.sh --
cat <<'EOF' > components/componentconfig-gitops.cue
-- componentconfig-gitops-trailer.sh --
EOF
-- componentconfig-gitops.cue --
package holos
import (
"path"
app "argoproj.io/application/v1alpha1"
)
parameters: {
env: string @tag(env)
}
// #ComponentConfig composes configuration into every Holos Component. Here we
// compose an ArgoCD Application resource along side each component to reconcile
// the hydrated manifests with the cluster.
#ComponentConfig: {
Name: _
OutputBaseDir: _
// Application resources are Environment scoped. Note the combination of
// component name and environment must be unique.
_ArgoAppName: "\(parameters.env)-\(Name)"
// Allow other aspects of the platform configuration to refer to
// `Component._ArgoApplication` to get a handle on the Application resource
// and mix additional configuration in.
_ArgoApplication: app.#Application & {
metadata: name: _ArgoAppName
metadata: namespace: "argocd"
metadata: labels: Labels
// Label the Application with the env so we can easily filter in the UI.
metadata: labels: env: parameters.env
spec: {
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L40
destination: server: "https://kubernetes.default.svc"
destination: namespace: parameters.env
project: "default"
// source migrated from sources
// https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L22-L35
source: {
path: string | *ResourcesPath
repoURL: "https://github.com/holos-run/multi-sources-example"
targetRevision: string | *"main"
}
// Migrated from https://github.com/holos-run/multi-sources-example/blob/v0.1.0/appsets/4-final/all-my-envs-appset-with-version.yaml#L43-L48
syncPolicy: syncOptions: ["CreateNamespace=true"]
syncPolicy: automated: prune: true
syncPolicy: automated: selfHeal: true
}
}
// We combine all Application resources into the deploy/gitops/ folder
// assuming Application.metadata.name is unique. This makes it easy to apply
// all of the hydrated Application resources in one shot.
let ArtifactPath = path.Join(["gitops", "\(_ArgoApplication.metadata.name)-application.gen.yaml"], path.Unix)
// Alternatively we could write the Applications along side the OutputBaseDir
// let ArtifactPath = path.Join([OutputBaseDir, "gitops", "\(Name)-application.gen.yaml"], path.Unix)
// ResourcesPath represents the configuration path the Application is
// configured to read as a source. This path contains the fully rendered
// manifests produced by Holos and written to the GitOps repo.
//
// For example, to reconcile my-chart.gen.yaml for prod-us:
// let ResourcesPath = "deploy/environments/prod-us/components/my-chart"
let ResourcesPath = path.Join(["deploy", OutputBaseDir, "components", Name], path.Unix)
// Add the argocd Application instance label to GitOps so resources are in sync.
// This is an example of how Holos makes it easy to add common labels to all
// resources regardless of if they come from Helm, CUE, Kustomize, plain YAML
// manifests, etc...
KustomizeConfig: CommonLabels: "argocd.argoproj.io/instance": _ArgoAppName
// Labels for the Application itself. We filter the argocd application
// instance label so ArgoCD doesn't think the Application resource manages
// itself.
let Labels = {
for k, v in KustomizeConfig.CommonLabels {
if k != "argocd.argoproj.io/instance" {
(k): v
}
}
}
Artifacts: "\(Name)-application": {
artifact: ArtifactPath
generators: [{
kind: "Resources"
output: artifact
resources: Application: (_ArgoAppName): _ArgoApplication
}]
}
}

View File

@@ -0,0 +1,2 @@
git clone https://github.com/holos-run/multi-sources-example.git
cd multi-sources-example

View File

@@ -0,0 +1 @@
Cloning into 'multi-sources-example'...

View File

@@ -0,0 +1 @@
f35da50452b346d4eea3f3e59ff5ae6b8c221218

View File

@@ -0,0 +1,2 @@
git branch -f work start
git checkout work

Some files were not shown because too many files have changed in this diff Show More