Skip to content

Commit 1fdd126

Browse files
committed
feat: derive spiffe-helper-config CM from PlatformConfig
Add GetPlatformConfig to AgentRuntimeReconciler and ensureSpiffeHelperConfigMap() which creates/overwrites the spiffe-helper-config CM per namespace from PlatformConfig. Remove spiffe-helper-config from templateConfigMapNames (no longer template-copied). Include helperConfig in config hash so changes trigger rolling updates. RHAIENG-4939 Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Ian Miller <milleryan2003@gmail.com>
1 parent b063a51 commit 1fdd126

5 files changed

Lines changed: 219 additions & 4 deletions

File tree

kagenti-operator/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ func main() {
631631
EnableCardDiscovery: enableCardDiscovery,
632632
SpireTrustDomain: spireTrustDomain,
633633
GetFeatureGates: featureGateLoader.Get,
634+
GetPlatformConfig: configLoader.Get,
634635
}
635636
if enableCardDiscovery {
636637
artReconciler.AgentFetcher = agentFetcher

kagenti-operator/internal/controller/agentruntime_config.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ const (
4848
// ConfigMap are watched by AgentRuntimeReconciler so the resolved-config
4949
// hash picks them up and rolls affected workloads.
5050
AuthBridgeRuntimeConfigMapName = "authbridge-runtime-config"
51+
52+
// SpiffeHelperConfigMapName is the namespace-scoped ConfigMap holding
53+
// the spiffe-helper helper.conf. Derived from PlatformConfig by the
54+
// controller; included in the config hash for rolling updates.
55+
SpiffeHelperConfigMapName = "spiffe-helper-config"
5156
)
5257

5358
// ClusterDefaultsNamespace is the namespace where cluster-level ConfigMaps
@@ -84,6 +89,10 @@ type resolvedConfig struct {
8489
// we want any byte change to roll the workload. Empty string when
8590
// the ConfigMap doesn't exist in the namespace.
8691
AuthBridgeRuntime string `json:"authBridgeRuntime,omitempty"`
92+
93+
// SpiffeHelperConfig captures the spiffe-helper-config CM content so
94+
// changes to PlatformConfig's spiffe.helperConfig trigger rolling updates.
95+
SpiffeHelperConfig string `json:"spiffeHelperConfig,omitempty"`
8796
}
8897

8998
// ConfigResult holds the computed hash and any warnings from the config resolution.
@@ -130,10 +139,19 @@ func resolveConfig(ctx context.Context, c client.Reader, namespace string) (reso
130139
abRuntime = data["config.yaml"]
131140
}
132141

142+
// Layer 2c: spiffe-helper-config (helper.conf).
143+
// Derived from PlatformConfig by the controller; included in hash
144+
// so changes to spiffe.helperConfig trigger rolling updates.
145+
spiffeHelper := ""
146+
if data := readConfigMapData(ctx, c, namespace, SpiffeHelperConfigMapName); len(data) > 0 {
147+
spiffeHelper = data["helper.conf"]
148+
}
149+
133150
return resolvedConfig{
134-
FeatureGates: featureGates,
135-
Defaults: merged,
136-
AuthBridgeRuntime: abRuntime,
151+
FeatureGates: featureGates,
152+
Defaults: merged,
153+
AuthBridgeRuntime: abRuntime,
154+
SpiffeHelperConfig: spiffeHelper,
137155
}, warnings
138156
}
139157

kagenti-operator/internal/controller/agentruntime_config_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,32 @@ var _ = Describe("AgentRuntime Config", func() {
220220
r2, _ := ComputeConfigHash(ctx, k8sClient, namespace)
221221
Expect(r1.Hash).NotTo(Equal(r2.Hash))
222222
})
223+
224+
It("should change when spiffe-helper-config content changes", func() {
225+
const shHashNS = "sh-hash-ns"
226+
shNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: shHashNS}}
227+
_ = k8sClient.Create(ctx, shNS)
228+
229+
shCM := &corev1.ConfigMap{
230+
ObjectMeta: metav1.ObjectMeta{
231+
Name: "spiffe-helper-config",
232+
Namespace: shHashNS,
233+
},
234+
Data: map[string]string{
235+
"helper.conf": "agent_address = \"/old/socket\"",
236+
},
237+
}
238+
Expect(k8sClient.Create(ctx, shCM)).To(Succeed())
239+
defer func() { _ = k8sClient.Delete(ctx, shCM) }()
240+
241+
r1, _ := ComputeConfigHash(ctx, k8sClient, shHashNS)
242+
243+
shCM.Data["helper.conf"] = "agent_address = \"/new/socket\""
244+
Expect(k8sClient.Update(ctx, shCM)).To(Succeed())
245+
246+
r2, _ := ComputeConfigHash(ctx, k8sClient, shHashNS)
247+
Expect(r1.Hash).NotTo(Equal(r2.Hash))
248+
})
223249
})
224250

225251
Context("resolveConfig two-layer merge", func() {

kagenti-operator/internal/controller/agentruntime_controller.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ type AgentRuntimeReconciler struct {
109109
EnableCardDiscovery bool
110110
SpireTrustDomain string
111111
GetFeatureGates func() *webhookconfig.FeatureGates
112+
GetPlatformConfig func() *webhookconfig.PlatformConfig
112113
}
113114

114115
func (r *AgentRuntimeReconciler) getFeatureGates() *webhookconfig.FeatureGates {
@@ -118,6 +119,15 @@ func (r *AgentRuntimeReconciler) getFeatureGates() *webhookconfig.FeatureGates {
118119
return webhookconfig.DefaultFeatureGates()
119120
}
120121

122+
func (r *AgentRuntimeReconciler) getPlatformConfig() *webhookconfig.PlatformConfig {
123+
if r.GetPlatformConfig != nil {
124+
if cfg := r.GetPlatformConfig(); cfg != nil {
125+
return cfg
126+
}
127+
}
128+
return webhookconfig.CompiledDefaults()
129+
}
130+
121131
// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes,verbs=get;list;watch;create;update;patch;delete
122132
// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes/status,verbs=get;update;patch
123133
// +kubebuilder:rbac:groups=agent.kagenti.dev,resources=agentruntimes/finalizers,verbs=update
@@ -190,6 +200,17 @@ func (r *AgentRuntimeReconciler) Reconcile(ctx context.Context, req ctrl.Request
190200
}
191201
}
192202

203+
// 4.5b. Ensure spiffe-helper-config CM is derived from PlatformConfig.
204+
// Unlike template CMs above, this always overwrites to keep PlatformConfig
205+
// as the single source of truth.
206+
if err := r.ensureSpiffeHelperConfigMap(ctx, rt.Namespace); err != nil {
207+
logger.Error(err, "Failed to ensure spiffe-helper-config")
208+
if r.Recorder != nil {
209+
r.Recorder.Eventf(rt, nil, corev1.EventTypeWarning, "ConfigMapEnsureError",
210+
"EnsureSpiffeHelperConfig", err.Error())
211+
}
212+
}
213+
193214
// 4.6. Ensure namespace has Istio ambient mesh labels for ztunnel mTLS.
194215
istioLabeled, istioErr := r.ensureIstioMeshLabels(ctx, rt.Namespace)
195216
switch {
@@ -1024,6 +1045,59 @@ func (r *AgentRuntimeReconciler) ensureNamespaceConfigMaps(ctx context.Context,
10241045
return nil
10251046
}
10261047

1048+
// ensureSpiffeHelperConfigMap creates or updates the spiffe-helper-config ConfigMap
1049+
// in the target namespace using content from PlatformConfig. Unlike template CMs
1050+
// which are create-if-not-exists, this always overwrites because PlatformConfig is
1051+
// the single source of truth.
1052+
func (r *AgentRuntimeReconciler) ensureSpiffeHelperConfigMap(ctx context.Context, namespace string) error {
1053+
logger := log.FromContext(ctx)
1054+
cfg := r.getPlatformConfig()
1055+
1056+
desired := &corev1.ConfigMap{
1057+
ObjectMeta: metav1.ObjectMeta{
1058+
Name: SpiffeHelperConfigMapName,
1059+
Namespace: namespace,
1060+
Labels: map[string]string{
1061+
LabelManagedBy: LabelManagedByValue,
1062+
},
1063+
},
1064+
Data: map[string]string{
1065+
"helper.conf": cfg.Spiffe.HelperConfig,
1066+
},
1067+
}
1068+
1069+
existing := &corev1.ConfigMap{}
1070+
err := r.uncachedReader().Get(ctx, client.ObjectKey{Namespace: namespace, Name: SpiffeHelperConfigMapName}, existing)
1071+
if apierrors.IsNotFound(err) {
1072+
if err := r.Create(ctx, desired); err != nil {
1073+
if apierrors.IsAlreadyExists(err) {
1074+
return nil
1075+
}
1076+
return fmt.Errorf("failed to create spiffe-helper-config in %s: %w", namespace, err)
1077+
}
1078+
logger.Info("Created spiffe-helper-config from PlatformConfig", "namespace", namespace)
1079+
return nil
1080+
}
1081+
if err != nil {
1082+
return fmt.Errorf("failed to check spiffe-helper-config in %s: %w", namespace, err)
1083+
}
1084+
1085+
if existing.Data["helper.conf"] == cfg.Spiffe.HelperConfig {
1086+
return nil
1087+
}
1088+
1089+
existing.Data = desired.Data
1090+
if existing.Labels == nil {
1091+
existing.Labels = make(map[string]string)
1092+
}
1093+
existing.Labels[LabelManagedBy] = LabelManagedByValue
1094+
if err := r.Update(ctx, existing); err != nil {
1095+
return fmt.Errorf("failed to update spiffe-helper-config in %s: %w", namespace, err)
1096+
}
1097+
logger.Info("Updated spiffe-helper-config from PlatformConfig", "namespace", namespace)
1098+
return nil
1099+
}
1100+
10271101
const (
10281102
sccClusterRoleName = "system:openshift:scc:kagenti-authbridge"
10291103
sccRoleBindingName = "agent-authbridge-scc"

kagenti-operator/internal/controller/agentruntime_controller_test.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ var _ = Describe("AgentRuntime Controller", func() {
704704
})
705705

706706
AfterEach(func() {
707-
for _, name := range templateConfigMapNames {
707+
for _, name := range []string{"authbridge-config", "authbridge-runtime-config", "envoy-config", "spiffe-helper-config"} {
708708
cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ClusterDefaultsNamespace}}
709709
_ = k8sClient.Delete(ctx, cm)
710710
cm = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: cmTestNS}}
@@ -819,6 +819,102 @@ var _ = Describe("AgentRuntime Controller", func() {
819819
})
820820
})
821821

822+
Context("When ensuring spiffe-helper-config from PlatformConfig", func() {
823+
const shTestNS = "sh-test-ns"
824+
825+
BeforeEach(func() {
826+
testNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: shTestNS}}
827+
_ = k8sClient.Create(ctx, testNS)
828+
})
829+
830+
AfterEach(func() {
831+
cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "spiffe-helper-config", Namespace: shTestNS}}
832+
_ = k8sClient.Delete(ctx, cm)
833+
})
834+
835+
It("should create spiffe-helper-config when missing", func() {
836+
r := newReconciler()
837+
r.GetPlatformConfig = func() *webhookconfig.PlatformConfig {
838+
cfg := webhookconfig.CompiledDefaults()
839+
cfg.Spiffe.HelperConfig = "test_agent_address = \"/test/socket\""
840+
return cfg
841+
}
842+
843+
Expect(r.ensureSpiffeHelperConfigMap(ctx, shTestNS)).To(Succeed())
844+
845+
created := &corev1.ConfigMap{}
846+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "spiffe-helper-config", Namespace: shTestNS}, created)).To(Succeed())
847+
Expect(created.Data["helper.conf"]).To(Equal("test_agent_address = \"/test/socket\""))
848+
Expect(created.Labels[LabelManagedBy]).To(Equal(LabelManagedByValue))
849+
})
850+
851+
It("should overwrite spiffe-helper-config when content differs", func() {
852+
existing := &corev1.ConfigMap{
853+
ObjectMeta: metav1.ObjectMeta{
854+
Name: "spiffe-helper-config",
855+
Namespace: shTestNS,
856+
},
857+
Data: map[string]string{"helper.conf": "old_content"},
858+
}
859+
Expect(k8sClient.Create(ctx, existing)).To(Succeed())
860+
861+
r := newReconciler()
862+
r.GetPlatformConfig = func() *webhookconfig.PlatformConfig {
863+
cfg := webhookconfig.CompiledDefaults()
864+
cfg.Spiffe.HelperConfig = "new_content"
865+
return cfg
866+
}
867+
868+
Expect(r.ensureSpiffeHelperConfigMap(ctx, shTestNS)).To(Succeed())
869+
870+
updated := &corev1.ConfigMap{}
871+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "spiffe-helper-config", Namespace: shTestNS}, updated)).To(Succeed())
872+
Expect(updated.Data["helper.conf"]).To(Equal("new_content"))
873+
})
874+
875+
It("should be idempotent when content matches", func() {
876+
existing := &corev1.ConfigMap{
877+
ObjectMeta: metav1.ObjectMeta{
878+
Name: "spiffe-helper-config",
879+
Namespace: shTestNS,
880+
Labels: map[string]string{LabelManagedBy: LabelManagedByValue},
881+
},
882+
Data: map[string]string{"helper.conf": "same_content"},
883+
}
884+
Expect(k8sClient.Create(ctx, existing)).To(Succeed())
885+
886+
r := newReconciler()
887+
r.GetPlatformConfig = func() *webhookconfig.PlatformConfig {
888+
cfg := webhookconfig.CompiledDefaults()
889+
cfg.Spiffe.HelperConfig = "same_content"
890+
return cfg
891+
}
892+
893+
Expect(r.ensureSpiffeHelperConfigMap(ctx, shTestNS)).To(Succeed())
894+
895+
result := &corev1.ConfigMap{}
896+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "spiffe-helper-config", Namespace: shTestNS}, result)).To(Succeed())
897+
Expect(result.Data["helper.conf"]).To(Equal("same_content"))
898+
})
899+
900+
It("should use CompiledDefaults when GetPlatformConfig is nil", func() {
901+
r := newReconciler()
902+
Expect(r.ensureSpiffeHelperConfigMap(ctx, shTestNS)).To(Succeed())
903+
904+
created := &corev1.ConfigMap{}
905+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "spiffe-helper-config", Namespace: shTestNS}, created)).To(Succeed())
906+
Expect(created.Data["helper.conf"]).To(Equal(webhookconfig.DefaultSpiffeHelperConfig))
907+
})
908+
})
909+
910+
Context("When templateConfigMapNames excludes spiffe-helper-config", func() {
911+
It("should not contain spiffe-helper-config", func() {
912+
for _, name := range templateConfigMapNames {
913+
Expect(name).NotTo(Equal("spiffe-helper-config"))
914+
}
915+
})
916+
})
917+
822918
Context("Service resolution for card discovery", func() {
823919
It("should resolve service by name match", func() {
824920
dep := newDeployment("svc-name-deploy", namespace)

0 commit comments

Comments
 (0)