Skip to content

Commit 228d14c

Browse files
tmshortclaude
andcommitted
fix: address CodeRabbit review comments on proxy controller
Clear stale proxy env vars on annotation removal: previously, workloads that lost the inject-proxy annotation kept proxy env vars owned by the capi-operator-proxy SSA field manager indefinitely. Now when the annotation is absent the controller applies an empty env list to all containers in the pod spec, which removes any previously-owned HTTP_PROXY/HTTPS_PROXY/NO_PROXY entries. Objects that never had the annotation are unaffected (no ownership to clear). Continue reconciling remaining workloads after per-object patch errors: the three separate applyTo* functions (which also caused a duplication lint failure) are now merged into a single applyToAllWorkloads+collectWorkloads pair. Errors are accumulated per-object and joined rather than short- circuiting the rest of the reconcile loop. Add assertion message to SetupWithManager in suite_test.go so a BeforeSuite failure names the failing step clearly. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Todd Short <tshort@redhat.com>
1 parent b269f09 commit 228d14c

2 files changed

Lines changed: 116 additions & 76 deletions

File tree

pkg/controllers/installer/proxy_controller.go

Lines changed: 115 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package installer
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"strings"
2324

@@ -104,6 +105,18 @@ func SetupProxyController(mgr ctrl.Manager, trackingCache managedcache.TrackingC
104105
return nil
105106
}
106107

108+
// workloadItem collects the fields needed to reconcile proxy env vars on a
109+
// single Deployment, DaemonSet, or StatefulSet without storing a reference to
110+
// the concrete typed object.
111+
type workloadItem struct {
112+
apiVersion string
113+
kind string
114+
name string
115+
namespace string
116+
podAnnotations map[string]string
117+
containers []corev1.Container
118+
}
119+
107120
// Reconcile reads the cluster-wide proxy configuration and applies the proxy
108121
// environment variables to all managed workloads that carry the inject-proxy
109122
// annotation.
@@ -116,131 +129,158 @@ func (c *ProxyController) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.R
116129
return ctrl.Result{}, fmt.Errorf("fetching cluster-wide proxy: %w", err)
117130
}
118131

119-
var errs []error
120-
121-
for _, applyFn := range []func(context.Context, []corev1.EnvVar) error{
122-
c.applyToDeployments,
123-
c.applyToDaemonSets,
124-
c.applyToStatefulSets,
125-
} {
126-
if err := applyFn(ctx, proxyVars); err != nil {
127-
errs = append(errs, err)
128-
}
129-
}
130-
131-
if len(errs) > 0 {
132-
combined := errs[0]
133-
for _, e := range errs[1:] {
134-
combined = fmt.Errorf("%w; %w", combined, e)
135-
}
136-
137-
return ctrl.Result{}, combined
132+
if err := c.applyToAllWorkloads(ctx, proxyVars); err != nil {
133+
return ctrl.Result{}, err
138134
}
139135

140136
log.Info("Proxy environment variables reconciled")
141137

142138
return ctrl.Result{}, nil
143139
}
144140

145-
func (c *ProxyController) applyToDeployments(ctx context.Context, proxyVars []corev1.EnvVar) error {
146-
list := &appsv1.DeploymentList{}
147-
if err := c.trackingCache.List(ctx, list); err != nil {
148-
return fmt.Errorf("listing managed deployments: %w", err)
149-
}
150-
151-
for i := range list.Items {
152-
deploy := &list.Items[i]
153-
annotation, ok := deploy.Spec.Template.Annotations[ProxyInjectAnnotation]
141+
// containerNamesFromAnnotation parses the comma-separated inject-proxy annotation value.
142+
func containerNamesFromAnnotation(annotation string) []string {
143+
var names []string
154144

155-
if !ok {
156-
continue
145+
for _, name := range strings.Split(annotation, ",") {
146+
if n := strings.TrimSpace(name); n != "" {
147+
names = append(names, n)
157148
}
149+
}
158150

159-
if err := c.applyProxyVars(ctx, "apps/v1", "Deployment", deploy.Name, deploy.Namespace, annotation, proxyVars); err != nil {
160-
return err
161-
}
151+
return names
152+
}
153+
154+
// containerNamesFromSpec returns the names of all containers in a pod spec.
155+
func containerNamesFromSpec(containers []corev1.Container) []string {
156+
names := make([]string, len(containers))
157+
for i, c := range containers {
158+
names[i] = c.Name
162159
}
163160

164-
return nil
161+
return names
165162
}
166163

167-
func (c *ProxyController) applyToDaemonSets(ctx context.Context, proxyVars []corev1.EnvVar) error {
168-
list := &appsv1.DaemonSetList{}
169-
if err := c.trackingCache.List(ctx, list); err != nil {
170-
return fmt.Errorf("listing managed daemonsets: %w", err)
164+
// applyToAllWorkloads collects all managed Deployments, DaemonSets, and
165+
// StatefulSets from the tracking cache and reconciles their proxy env vars.
166+
// It continues past per-object errors and returns all failures combined.
167+
func (c *ProxyController) applyToAllWorkloads(ctx context.Context, proxyVars []corev1.EnvVar) error {
168+
items, err := c.collectWorkloads(ctx)
169+
if err != nil {
170+
return err
171171
}
172172

173-
for i := range list.Items {
174-
ds := &list.Items[i]
173+
var errs []error
174+
175+
for _, item := range items {
176+
var containers []string
175177

176-
annotation, ok := ds.Spec.Template.Annotations[ProxyInjectAnnotation]
178+
effectiveVars := proxyVars
177179

178-
if !ok {
179-
continue
180+
annotation, hasAnnotation := item.podAnnotations[ProxyInjectAnnotation]
181+
if hasAnnotation {
182+
containers = containerNamesFromAnnotation(annotation)
183+
} else {
184+
// No annotation: clear any stale proxy env vars we previously owned
185+
// so that opting out removes the env vars instead of leaving them.
186+
containers = containerNamesFromSpec(item.containers)
187+
effectiveVars = nil
180188
}
181189

182-
if err := c.applyProxyVars(ctx, "apps/v1", "DaemonSet", ds.Name, ds.Namespace, annotation, proxyVars); err != nil {
183-
return err
190+
if err := c.applyProxyVars(ctx, item.apiVersion, item.kind, item.name, item.namespace, containers, effectiveVars); err != nil {
191+
errs = append(errs, err)
184192
}
185193
}
186194

187-
return nil
195+
return errors.Join(errs...)
188196
}
189197

190-
func (c *ProxyController) applyToStatefulSets(ctx context.Context, proxyVars []corev1.EnvVar) error {
191-
list := &appsv1.StatefulSetList{}
192-
if err := c.trackingCache.List(ctx, list); err != nil {
193-
return fmt.Errorf("listing managed statefulsets: %w", err)
198+
// collectWorkloads lists all managed Deployments, DaemonSets, and StatefulSets
199+
// from the tracking cache and returns them as workloadItems.
200+
func (c *ProxyController) collectWorkloads(ctx context.Context) ([]workloadItem, error) {
201+
var items []workloadItem
202+
203+
deployList := &appsv1.DeploymentList{}
204+
if err := c.trackingCache.List(ctx, deployList); err != nil {
205+
return nil, fmt.Errorf("listing managed deployments: %w", err)
194206
}
195207

196-
for i := range list.Items {
197-
ss := &list.Items[i]
208+
for i := range deployList.Items {
209+
d := &deployList.Items[i]
210+
items = append(items, workloadItem{
211+
apiVersion: "apps/v1",
212+
kind: "Deployment",
213+
name: d.Name,
214+
namespace: d.Namespace,
215+
podAnnotations: d.Spec.Template.Annotations,
216+
containers: d.Spec.Template.Spec.Containers,
217+
})
218+
}
198219

199-
annotation, ok := ss.Spec.Template.Annotations[ProxyInjectAnnotation]
220+
dsList := &appsv1.DaemonSetList{}
221+
if err := c.trackingCache.List(ctx, dsList); err != nil {
222+
return nil, fmt.Errorf("listing managed daemonsets: %w", err)
223+
}
200224

201-
if !ok {
202-
continue
203-
}
225+
for i := range dsList.Items {
226+
ds := &dsList.Items[i]
227+
items = append(items, workloadItem{
228+
apiVersion: "apps/v1",
229+
kind: "DaemonSet",
230+
name: ds.Name,
231+
namespace: ds.Namespace,
232+
podAnnotations: ds.Spec.Template.Annotations,
233+
containers: ds.Spec.Template.Spec.Containers,
234+
})
235+
}
204236

205-
if err := c.applyProxyVars(ctx, "apps/v1", "StatefulSet", ss.Name, ss.Namespace, annotation, proxyVars); err != nil {
206-
return err
207-
}
237+
ssList := &appsv1.StatefulSetList{}
238+
if err := c.trackingCache.List(ctx, ssList); err != nil {
239+
return nil, fmt.Errorf("listing managed statefulsets: %w", err)
208240
}
209241

210-
return nil
242+
for i := range ssList.Items {
243+
ss := &ssList.Items[i]
244+
items = append(items, workloadItem{
245+
apiVersion: "apps/v1",
246+
kind: "StatefulSet",
247+
name: ss.Name,
248+
namespace: ss.Namespace,
249+
podAnnotations: ss.Spec.Template.Annotations,
250+
containers: ss.Spec.Template.Spec.Containers,
251+
})
252+
}
253+
254+
return items, nil
211255
}
212256

213-
// applyProxyVars SSA-applies the proxy env vars to each container named in the
214-
// comma-separated annotation value. The proxy controller owns only the three
215-
// proxy env var entries; all other fields remain owned by boxcutter.
257+
// applyProxyVars SSA-applies proxy env vars to each named container on the
258+
// workload object. When proxyVars is empty/nil (proxy removed or no annotation),
259+
// it applies an empty env list, which removes any env var entries previously
260+
// owned by the proxy controller's SSA field manager. Only the three proxy env
261+
// var entries are owned; all other fields remain owned by boxcutter.
216262
func (c *ProxyController) applyProxyVars(
217263
ctx context.Context,
218-
apiVersion, kind, name, namespace, annotation string,
264+
apiVersion, kind, name, namespace string,
265+
containerNames []string,
219266
proxyVars []corev1.EnvVar,
220267
) error {
221268
log := ctrl.LoggerFrom(ctx).WithName(proxyControllerName)
222269

223-
containerNames := strings.Split(annotation, ",")
224-
containerPatches := make([]map[string]interface{}, 0, len(containerNames))
270+
if len(containerNames) == 0 {
271+
return nil
272+
}
225273

274+
containerPatches := make([]map[string]interface{}, 0, len(containerNames))
226275
envEntries := proxyEnvEntries(proxyVars)
227276

228277
for _, containerName := range containerNames {
229-
containerName = strings.TrimSpace(containerName)
230-
if containerName == "" {
231-
continue
232-
}
233-
234278
containerPatches = append(containerPatches, map[string]interface{}{
235279
"name": containerName,
236280
"env": envEntries,
237281
})
238282
}
239283

240-
if len(containerPatches) == 0 {
241-
return nil
242-
}
243-
244284
patch := &unstructured.Unstructured{
245285
Object: map[string]interface{}{
246286
"apiVersion": apiVersion,

pkg/controllers/installer/suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ var _ = BeforeSuite(func() {
107107
)
108108

109109
_, err = SetupWithManager(mgr, allProviderProfiles, triggerSource)
110-
Expect(err).To(Succeed())
110+
Expect(err).To(Succeed(), "SetupWithManager should register the installer controller")
111111
Expect(test.AddNamespaceFinalizerCleanup(mgr)).To(Succeed())
112112

113113
// Start manager in background.

0 commit comments

Comments
 (0)