From ad3ba0d8cc6ec6de96e6b4d24018557355a1c2d9 Mon Sep 17 00:00:00 2001 From: Aline Abler Date: Tue, 29 Sep 2026 12:30:51 +0200 Subject: [PATCH 1/5] Add espejote-managed dynamic fact for cluster CA --- class/capi-provider-talos.yml | 1 + class/defaults.yml | 2 + component/ca-fact.jsonnet | 111 ++++++++++++++ .../espejote-templates/manage-ca-fact.jsonnet | 22 +++ tests/defaults.yml | 5 + .../cluster_ca_dynamic_fact.yaml | 144 ++++++++++++++++++ 6 files changed, 285 insertions(+) create mode 100644 component/ca-fact.jsonnet create mode 100644 component/espejote-templates/manage-ca-fact.jsonnet create mode 100644 tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml diff --git a/class/capi-provider-talos.yml b/class/capi-provider-talos.yml index 7f60020..4467bdb 100644 --- a/class/capi-provider-talos.yml +++ b/class/capi-provider-talos.yml @@ -7,6 +7,7 @@ parameters: output_path: . - input_paths: - ${_base_directory}/component/main.jsonnet + - ${_base_directory}/component/ca-fact.jsonnet input_type: jsonnet output_path: . - input_paths: diff --git a/class/defaults.yml b/class/defaults.yml index 5ce8a59..be5ce1a 100644 --- a/class/defaults.yml +++ b/class/defaults.yml @@ -4,6 +4,8 @@ parameters: multi_tenant: true namespace: syn-cluster-api + clusterName: ${cluster:name} + images: capi-bootstrap-provider-talos: registry: ghcr.io diff --git a/component/ca-fact.jsonnet b/component/ca-fact.jsonnet new file mode 100644 index 0000000..5803ebd --- /dev/null +++ b/component/ca-fact.jsonnet @@ -0,0 +1,111 @@ +local esp = import 'lib/espejote.libsonnet'; +local kap = import 'lib/kapitan.libjsonnet'; +local kube = import 'lib/kube.libjsonnet'; +local inv = kap.inventory(); + +local params = inv.parameters.capi_provider_talos; +local ca_secret_name = '%s-ca' % params.clusterName; + +local syn_namespace = 'syn'; + +local sa = kube.ServiceAccount('cluster-ca-dynamic-fact-manager') { + metadata+: { + namespace: params.namespace, + }, +}; + +local syn_role = kube.Role('cluster:ca-dynamic-fact-manager') { + metadata+: { + namespace: syn_namespace, + }, + rules: [ + { + apiGroups: [ '' ], + resources: [ 'configmaps' ], + verbs: [ 'get', 'list', 'watch' ], + }, + { + apiGroups: [ '' ], + resources: [ 'configmaps' ], + resourceNames: [ 'capi-ca-fact' ], + verbs: [ 'create', 'update', 'patch' ], + }, + ], +}; + +local role = kube.Role('cluster:ca-dynamic-fact-manager') { + metadata+: { + namespace: params.namespace, + }, + rules: [ + { + apiGroups: [ '' ], + resources: [ 'secrets' ], + resourceNames: [ ca_secret_name ], + verbs: [ 'get', 'list', 'watch' ], + }, + ], +}; + +local syn_rolebinding = + kube.RoleBinding('cluster:ca-dynamic-fact-manager') { + metadata+: { + namespace: syn_namespace, + }, + roleRef_: syn_role, + subjects_: [ sa ], + }; + +local rolebinding = + kube.RoleBinding('cluster:ca-dynamic-fact-manager') { + metadata+: { + namespace: params.namespace, + }, + roleRef_: role, + subjects_: [ sa ], + }; + + +local mr = esp.managedResource('cluster-ca-dynamic-fact', params.namespace) { + spec: { + // Set force=true so we can take ownership of previously manually edited + // fields in `data`. + applyOptions: { force: true }, + serviceAccountRef: { name: sa.metadata.name }, + context: [ + { + name: 'source', + resource: { + apiVersion: 'v1', + kind: 'Secret', + name: ca_secret_name, + }, + }, + ], + triggers: [ + { + name: 'source', + watchContextResource: { + name: 'source', + }, + }, + { + name: 'target', + watchResource: { + apiVersion: 'v1', + kind: 'ConfigMap', + namespace: syn_namespace, + name: 'capi-ca-fact', + }, + }, + ], + template: importstr 'espejote-templates/manage-ca-fact.jsonnet', + }, +}; + +if std.member(inv.applications, 'espejote') then { + cluster_ca_dynamic_fact: [ sa, role, rolebinding, syn_role, syn_rolebinding, mr ], +} else std.trace( + 'Not rendering Espejote-managed CA fact because component-espejote is missing.', + {} +) diff --git a/component/espejote-templates/manage-ca-fact.jsonnet b/component/espejote-templates/manage-ca-fact.jsonnet new file mode 100644 index 0000000..5ad067e --- /dev/null +++ b/component/espejote-templates/manage-ca-fact.jsonnet @@ -0,0 +1,22 @@ +local esp = import 'espejote.libsonnet'; +local src = + local srcs = esp.context().source; + assert std.length(srcs) == 1 : 'Expected source context to have length 1'; + srcs[0]; +{ + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'capi-ca-fact', + namespace: 'syn', + labels: { + 'app.kubernetes.io/managed-by': 'espejote', + 'steward.syn.tools/include-facts': '', + }, + }, + data: { + facts: std.manifestJsonMinified({ + talosAPICertificateAuthorityData: src.data['tls.crt'], + }), + }, +} diff --git a/tests/defaults.yml b/tests/defaults.yml index 5168b10..49aa40b 100644 --- a/tests/defaults.yml +++ b/tests/defaults.yml @@ -2,6 +2,7 @@ applications: - capi-core + - espejote parameters: kapitan: @@ -9,6 +10,10 @@ parameters: - type: https source: https://raw.githubusercontent.com/projectsyn/component-capi-core/master/lib/capi-core.libsonnet output_path: vendor/lib/capi-core.libsonnet + - type: https + source: https://raw.githubusercontent.com/projectsyn/component-espejote/master/lib/espejote.libsonnet + output_path: vendor/lib/espejote.libsonnet capi_core: + clusterName: c-holy-fire-1337 namespace: syn-cluster-api diff --git a/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml b/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml new file mode 100644 index 0000000..2e75fe3 --- /dev/null +++ b/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml @@ -0,0 +1,144 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: {} + labels: + name: cluster-ca-dynamic-fact-manager + name: cluster-ca-dynamic-fact-manager + namespace: syn-cluster-api +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: {} + labels: + name: cluster-ca-dynamic-fact-manager + name: cluster:ca-dynamic-fact-manager + namespace: syn-cluster-api +rules: + - apiGroups: + - '' + resourceNames: + - c-green-test-1234-ca + resources: + - secrets + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: {} + labels: + name: cluster-ca-dynamic-fact-manager + name: cluster:ca-dynamic-fact-manager + namespace: syn-cluster-api +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cluster:ca-dynamic-fact-manager +subjects: + - kind: ServiceAccount + name: cluster-ca-dynamic-fact-manager + namespace: syn-cluster-api +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + annotations: {} + labels: + name: cluster-ca-dynamic-fact-manager + name: cluster:ca-dynamic-fact-manager + namespace: syn +rules: + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - '' + resourceNames: + - capi-ca-fact + resources: + - configmaps + verbs: + - create + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + annotations: {} + labels: + name: cluster-ca-dynamic-fact-manager + name: cluster:ca-dynamic-fact-manager + namespace: syn +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cluster:ca-dynamic-fact-manager +subjects: + - kind: ServiceAccount + name: cluster-ca-dynamic-fact-manager + namespace: syn-cluster-api +--- +apiVersion: espejote.io/v1alpha1 +kind: ManagedResource +metadata: + annotations: + argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true + labels: + app.kubernetes.io/name: cluster-ca-dynamic-fact + name: cluster-ca-dynamic-fact + namespace: syn-cluster-api +spec: + applyOptions: + force: true + context: + - name: source + resource: + apiVersion: v1 + kind: Secret + name: c-green-test-1234-ca + serviceAccountRef: + name: cluster-ca-dynamic-fact-manager + template: | + local esp = import 'espejote.libsonnet'; + local src = + local srcs = esp.context().source; + assert std.length(srcs) == 1 : 'Expected source context to have length 1'; + srcs[0]; + { + apiVersion: 'v1', + kind: 'ConfigMap', + metadata: { + name: 'capi-ca-fact', + namespace: 'syn', + labels: { + 'app.kubernetes.io/managed-by': 'espejote', + 'steward.syn.tools/include-facts': '', + }, + }, + data: { + facts: std.manifestJsonMinified({ + talosAPICertificateAuthorityData: src.data['tls.crt'], + }), + }, + } + triggers: + - name: source + watchContextResource: + name: source + - name: target + watchResource: + apiVersion: v1 + kind: ConfigMap + name: capi-ca-fact + namespace: syn From a573ee56e7e0a2795902f8dd9d76cd780feb9367 Mon Sep 17 00:00:00 2001 From: Aline Abler Date: Tue, 29 Sep 2026 13:34:49 +0200 Subject: [PATCH 2/5] Apply batched suggestions from code review Co-authored-by: Simon Gerber --- component/ca-fact.jsonnet | 12 ++++++------ component/espejote-templates/manage-ca-fact.jsonnet | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/component/ca-fact.jsonnet b/component/ca-fact.jsonnet index 5803ebd..89b5fac 100644 --- a/component/ca-fact.jsonnet +++ b/component/ca-fact.jsonnet @@ -14,7 +14,7 @@ local sa = kube.ServiceAccount('cluster-ca-dynamic-fact-manager') { }, }; -local syn_role = kube.Role('cluster:ca-dynamic-fact-manager') { +local syn_role = kube.Role('capi-provider-talos:ca-dynamic-fact-manager') { metadata+: { namespace: syn_namespace, }, @@ -27,13 +27,13 @@ local syn_role = kube.Role('cluster:ca-dynamic-fact-manager') { { apiGroups: [ '' ], resources: [ 'configmaps' ], - resourceNames: [ 'capi-ca-fact' ], + resourceNames: [ 'capi-talos-ca-fact' ], verbs: [ 'create', 'update', 'patch' ], }, ], }; -local role = kube.Role('cluster:ca-dynamic-fact-manager') { +local role = kube.Role('capi-provider-talos:ca-dynamic-fact-manager') { metadata+: { namespace: params.namespace, }, @@ -48,7 +48,7 @@ local role = kube.Role('cluster:ca-dynamic-fact-manager') { }; local syn_rolebinding = - kube.RoleBinding('cluster:ca-dynamic-fact-manager') { + kube.RoleBinding('capi-provider-talos:ca-dynamic-fact-manager') { metadata+: { namespace: syn_namespace, }, @@ -57,7 +57,7 @@ local syn_rolebinding = }; local rolebinding = - kube.RoleBinding('cluster:ca-dynamic-fact-manager') { + kube.RoleBinding('capi-provider-talos:ca-dynamic-fact-manager') { metadata+: { namespace: params.namespace, }, @@ -95,7 +95,7 @@ local mr = esp.managedResource('cluster-ca-dynamic-fact', params.namespace) { apiVersion: 'v1', kind: 'ConfigMap', namespace: syn_namespace, - name: 'capi-ca-fact', + name: 'capi-talos-ca-fact', }, }, ], diff --git a/component/espejote-templates/manage-ca-fact.jsonnet b/component/espejote-templates/manage-ca-fact.jsonnet index 5ad067e..00727ce 100644 --- a/component/espejote-templates/manage-ca-fact.jsonnet +++ b/component/espejote-templates/manage-ca-fact.jsonnet @@ -7,7 +7,7 @@ local src = apiVersion: 'v1', kind: 'ConfigMap', metadata: { - name: 'capi-ca-fact', + name: 'capi-talos-ca-fact', namespace: 'syn', labels: { 'app.kubernetes.io/managed-by': 'espejote', From 9ca01b1b412b178d652d7f232aeae5613ba15a9b Mon Sep 17 00:00:00 2001 From: Aline Abler Date: Tue, 29 Sep 2026 13:35:57 +0200 Subject: [PATCH 3/5] Regenerate golden tests --- .../cluster_ca_dynamic_fact.yaml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml b/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml index 2e75fe3..0fa82df 100644 --- a/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml +++ b/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml @@ -12,8 +12,8 @@ kind: Role metadata: annotations: {} labels: - name: cluster-ca-dynamic-fact-manager - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos-ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager namespace: syn-cluster-api rules: - apiGroups: @@ -32,13 +32,13 @@ kind: RoleBinding metadata: annotations: {} labels: - name: cluster-ca-dynamic-fact-manager - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos-ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager namespace: syn-cluster-api roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager subjects: - kind: ServiceAccount name: cluster-ca-dynamic-fact-manager @@ -49,8 +49,8 @@ kind: Role metadata: annotations: {} labels: - name: cluster-ca-dynamic-fact-manager - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos-ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager namespace: syn rules: - apiGroups: @@ -64,7 +64,7 @@ rules: - apiGroups: - '' resourceNames: - - capi-ca-fact + - capi-talos-ca-fact resources: - configmaps verbs: @@ -77,13 +77,13 @@ kind: RoleBinding metadata: annotations: {} labels: - name: cluster-ca-dynamic-fact-manager - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos-ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager namespace: syn roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: cluster:ca-dynamic-fact-manager + name: capi-provider-talos:ca-dynamic-fact-manager subjects: - kind: ServiceAccount name: cluster-ca-dynamic-fact-manager @@ -119,7 +119,7 @@ spec: apiVersion: 'v1', kind: 'ConfigMap', metadata: { - name: 'capi-ca-fact', + name: 'capi-talos-ca-fact', namespace: 'syn', labels: { 'app.kubernetes.io/managed-by': 'espejote', @@ -140,5 +140,5 @@ spec: watchResource: apiVersion: v1 kind: ConfigMap - name: capi-ca-fact + name: capi-talos-ca-fact namespace: syn From 760ae2e496cdfd84d14a3362d567f5b8e62d2f93 Mon Sep 17 00:00:00 2001 From: Aline Abler Date: Tue, 29 Sep 2026 14:53:30 +0200 Subject: [PATCH 4/5] Add documentation --- docs/modules/ROOT/pages/references/parameters.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/modules/ROOT/pages/references/parameters.adoc b/docs/modules/ROOT/pages/references/parameters.adoc index dd5e322..2b087bd 100644 --- a/docs/modules/ROOT/pages/references/parameters.adoc +++ b/docs/modules/ROOT/pages/references/parameters.adoc @@ -11,6 +11,16 @@ default:: `syn-cluster-api` The namespace in which to deploy this component. The component assumes that this namespace already exists via dependency `component-capi-core`. +== `clusterName` + +[horizontal] +type:: string +default:: `${cluster:name}` + +The name of the cluster this component is installed on. +If Espejote is installed, this component will automatically expose the cluster's CA certificate data as an additional fact in Steward. +It uses Espejote to manage the additional fact, and uses the provided cluster name to identify the correct CA secret. + == `images` [horizontal] From 29e6f8121ea19e75d3e17d9bbfa16ec80bf804ea Mon Sep 17 00:00:00 2001 From: Aline Abler Date: Tue, 29 Sep 2026 15:14:28 +0200 Subject: [PATCH 5/5] Fix output paths --- class/capi-provider-talos.yml | 5 ++++- .../{ => capi-provider-talos}/cluster_ca_dynamic_fact.yaml | 0 2 files changed, 4 insertions(+), 1 deletion(-) rename tests/golden/defaults/capi-provider-talos/{ => capi-provider-talos}/cluster_ca_dynamic_fact.yaml (100%) diff --git a/class/capi-provider-talos.yml b/class/capi-provider-talos.yml index 4467bdb..b08385b 100644 --- a/class/capi-provider-talos.yml +++ b/class/capi-provider-talos.yml @@ -7,7 +7,6 @@ parameters: output_path: . - input_paths: - ${_base_directory}/component/main.jsonnet - - ${_base_directory}/component/ca-fact.jsonnet input_type: jsonnet output_path: . - input_paths: @@ -52,6 +51,10 @@ parameters: args: - ${_base_directory}/manifests/capi-controlplane-provider-talos-rendered - \${compiled_target_dir}/capi-provider-talos/controlplane/10_kustomize + - input_paths: + - ${_base_directory}/component/ca-fact.jsonnet + input_type: jsonnet + output_path: ${_instance}/ # Cleanup intermediate manifests - input_type: remove diff --git a/tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml b/tests/golden/defaults/capi-provider-talos/capi-provider-talos/cluster_ca_dynamic_fact.yaml similarity index 100% rename from tests/golden/defaults/capi-provider-talos/cluster_ca_dynamic_fact.yaml rename to tests/golden/defaults/capi-provider-talos/capi-provider-talos/cluster_ca_dynamic_fact.yaml