Skip to content
This repository was archived by the owner on Aug 13, 2025. It is now read-only.

Commit cca6e16

Browse files
committed
added secret reconcile and fix configmap reconcile
1 parent afe0c32 commit cca6e16

6 files changed

Lines changed: 323 additions & 54 deletions

File tree

cmd/main.go

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ import (
4343
"uburro.github.com/fluxcd-trigger-operator/internal/controller"
4444

4545
helmv2 "github.com/fluxcd/helm-controller/api/v2"
46+
47+
"go.uber.org/zap/zapcore"
4648
)
4749

4850
var (
49-
scheme = runtime.NewScheme()
50-
setupLog = ctrl.Log.WithName("setup")
51+
scheme = runtime.NewScheme()
5152
)
5253

5354
type Config struct {
@@ -56,11 +57,14 @@ type Config struct {
5657
metricsAddr string
5758
metricsCertPath, metricsCertName, metricsCertKey string
5859
webhookCertPath, webhookCertName, webhookCertKey string
59-
enableLeaderElection bool
60+
logFormat string
61+
logLevel string
6062
probeAddr string
63+
enableLeaderElection bool
6164
secureMetrics bool
6265
enableHTTP2 bool
63-
tlsOpts []func(*tls.Config)
66+
67+
tlsOpts []func(*tls.Config)
6468
}
6569

6670
func init() {
@@ -70,6 +74,40 @@ func init() {
7074
// +kubebuilder:scaffold:scheme
7175
}
7276

77+
func getZapOpts(logFormat, logLevel string) []zap.Opts {
78+
var zapOpts []zap.Opts
79+
80+
// Configure log format
81+
if logFormat == "json" {
82+
zapOpts = append(zapOpts, zap.UseDevMode(false))
83+
} else {
84+
zapOpts = append(zapOpts, zap.UseDevMode(true))
85+
}
86+
87+
// Configure log level
88+
var level zapcore.Level
89+
switch logLevel {
90+
case "debug":
91+
level = zapcore.DebugLevel
92+
case "info":
93+
level = zapcore.InfoLevel
94+
case "warn":
95+
level = zapcore.WarnLevel
96+
case "error":
97+
level = zapcore.ErrorLevel
98+
case "dpanic":
99+
level = zapcore.DPanicLevel
100+
case "panic":
101+
level = zapcore.PanicLevel
102+
case "fatal":
103+
level = zapcore.FatalLevel
104+
default:
105+
level = zapcore.InfoLevel
106+
}
107+
zapOpts = append(zapOpts, zap.Level(level))
108+
return zapOpts
109+
}
110+
73111
// nolint:gocyclo
74112
func main() {
75113
config := Config{
@@ -101,13 +139,12 @@ func main() {
101139
"The name of the metrics server key file.")
102140
flag.BoolVar(&config.enableHTTP2, "enable-http2", false,
103141
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
104-
opts := zap.Options{
105-
Development: true,
106-
}
107-
opts.BindFlags(flag.CommandLine)
142+
flag.StringVar(&config.logFormat, "log-format", "plain", "Log format: plain or json")
143+
flag.StringVar(&config.logLevel, "log-level", "info", "Log level: debug, info, warn, error, dpanic, panic, fatal")
108144
flag.Parse()
109-
110-
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
145+
opts := getZapOpts(config.logFormat, config.logLevel)
146+
ctrl.SetLogger(zap.New(opts...))
147+
setupLog := ctrl.Log.WithName("setup")
111148

112149
// if the enable-http2 flag is false (the default), http/2 should be disabled
113150
// due to its vulnerabilities. More specifically, disabling http/2 will
@@ -258,6 +295,14 @@ func main() {
258295
os.Exit(1)
259296
}
260297

298+
if err = controller.NewSecretReconciler(
299+
mgr.GetClient(),
300+
mgr.GetScheme(),
301+
).SetupWithManager(mgr, config.MaxConcurrency, config.CacheSyncTimeout); err != nil {
302+
setupLog.Error(err, "unable to create controller", "controller", controller.ConfigMapReconcilerName)
303+
os.Exit(1)
304+
}
305+
261306
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
262307
setupLog.Error(err, "unable to set up health check")
263308
os.Exit(1)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
k8s.io/api v0.33.0
1212
k8s.io/apimachinery v0.33.0
1313
k8s.io/client-go v0.33.0
14+
k8s.io/kubernetes v1.33.2
1415
sigs.k8s.io/controller-runtime v0.21.0
1516
)
1617

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
245245
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
246246
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
247247
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
248+
k8s.io/kubernetes v1.33.2 h1:Vk3hsCaazyMQ6CXhu029AEPlBoYsEnD8oEIC0bP2pWQ=
249+
k8s.io/kubernetes v1.33.2/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00=
248250
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
249251
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
250252
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=

internal/controller/common.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"hash/adler32"
6+
"sync"
7+
"time"
8+
9+
"k8s.io/client-go/util/workqueue"
10+
"k8s.io/kubernetes/pkg/util/hash"
11+
"sigs.k8s.io/controller-runtime/pkg/controller"
12+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
13+
)
14+
15+
const (
16+
LabelReconcilerNameSourceKey = "uburro.github.com/fluxcd-trigger-operator"
17+
HRAnnotation = "uburro.github.com/helmreleases-name"
18+
NSAnnotation = "uburro.github.com/helmreleases-namespace"
19+
HashAnnotation = "uburro.github.com/config-digest"
20+
DefaultFluxcdNamespace = "flux-system"
21+
)
22+
23+
var (
24+
optionsInit sync.Once
25+
defaultOptions *controller.Options
26+
)
27+
28+
func ControllerOptions(maxConcurrency int, cacheSyncTimeout time.Duration) controller.Options {
29+
rateLimiters := workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](30*time.Second, 5*time.Minute)
30+
optionsInit.Do(func() {
31+
defaultOptions = &controller.Options{
32+
RateLimiter: rateLimiters,
33+
CacheSyncTimeout: cacheSyncTimeout,
34+
MaxConcurrentReconciles: maxConcurrency,
35+
}
36+
})
37+
return *defaultOptions
38+
}
39+
40+
func getDataHashCM(data map[string]string) string {
41+
hasher := adler32.New()
42+
hash.DeepHashObject(hasher, data)
43+
return fmt.Sprintf("%x", hasher.Sum32())
44+
}
45+
46+
func getDataHashSecret(data map[string][]byte) string {
47+
hasher := adler32.New()
48+
hash.DeepHashObject(hasher, data)
49+
return fmt.Sprintf("%x", hasher.Sum32())
50+
}

internal/controller/controller.go renamed to internal/controller/configmapcontroller.go

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,22 @@ package controller
22

33
import (
44
"context"
5-
"sync"
65
"time"
76

87
corev1 "k8s.io/api/core/v1"
98
"k8s.io/apimachinery/pkg/runtime"
10-
"k8s.io/client-go/util/workqueue"
119
ctrl "sigs.k8s.io/controller-runtime"
1210
"sigs.k8s.io/controller-runtime/pkg/builder"
1311
"sigs.k8s.io/controller-runtime/pkg/client"
14-
"sigs.k8s.io/controller-runtime/pkg/controller"
1512
"sigs.k8s.io/controller-runtime/pkg/event"
1613
"sigs.k8s.io/controller-runtime/pkg/log"
1714
"sigs.k8s.io/controller-runtime/pkg/predicate"
18-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
1915

2016
helmv2 "github.com/fluxcd/helm-controller/api/v2"
2117
)
2218

2319
const (
24-
ConfigMapReconcilerName = "configmap-reconciler"
25-
LabelConfigMapReconcilerNameSourceKey = "uburro.github.com/fluxcd-trigger-operator"
26-
AnnotationHelmReleaseNameKey = "uburro.github.com/helmreleases-name"
27-
AnnotationHelmReleaseNamespaceKey = "uburro.github.com/helmreleases-namespace"
28-
AnnotationDigistKey = "uburro.github.com/config-digest"
29-
DefaultFluxcdNamespace = "flux-system"
20+
ConfigMapReconcilerName = "configmap-reconciler"
3021
)
3122

3223
// ConfigMapReconciler reconciles ConfigMap resources in a Kubernetes cluster.
@@ -56,8 +47,8 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
5647
return ctrl.Result{}, err
5748
}
5849

59-
hrName := configMap.GetAnnotations()[AnnotationHelmReleaseNameKey]
60-
hrNamespace := configMap.GetAnnotations()[AnnotationHelmReleaseNamespaceKey]
50+
hrName := configMap.GetAnnotations()[HRAnnotation]
51+
hrNamespace := configMap.GetAnnotations()[NSAnnotation]
6152

6253
if hrName == "" {
6354
logger.V(1).Info("No HelmRelease name found in ConfigMap annotations", "configMap", req.Name)
@@ -73,32 +64,56 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
7364
Name: hrName,
7465
Namespace: hrNamespace,
7566
}, hr); err != nil {
76-
logger.V(1).Error(err, "Getting HelmRelease produced an error", "helmRelease", hrName, "namespace", hrNamespace)
67+
logger.V(1).Error(err, "Getting HelmRelease produced an error",
68+
"helmRelease", hrName, "namespace", hrNamespace)
7769
return ctrl.Result{}, err
7870
}
7971

80-
oldDigest := configMap.GetAnnotations()[AnnotationDigistKey]
81-
newDigest := hr.Status.History[0].Digest
72+
if len(hr.Status.History) == 0 {
73+
logger.V(1).Info("No history found in HelmRelease, skipping helmRelease patch",
74+
"helmRelease", hrName, "namespace", hrNamespace)
75+
return ctrl.Result{}, nil
76+
}
77+
78+
if hr.Status.History[0].Status != "deployed" {
79+
logger.V(1).Info("HelmRelease is not deployed, skipping patch", "helmRelease",
80+
hrName, "namespace", hrNamespace, "status", hr.Status.History[0].Status)
81+
return ctrl.Result{}, nil
82+
}
83+
84+
oldDigest := configMap.GetAnnotations()[HashAnnotation]
85+
newDigest := getDataHashCM(configMap.Data)
86+
87+
logger.V(1).Info("old digest", "digest", oldDigest)
88+
logger.V(1).Info("new digest", "digest", newDigest)
8289

8390
if oldDigest == newDigest {
8491
logger.V(1).Info("No changes detected in ConfigMap, skipping HelmRelease patch", "configMap", req.Name)
8592
return ctrl.Result{}, nil
8693
}
8794

88-
patchTarget := hr.DeepCopy()
95+
patchTargetHR := hr.DeepCopy()
8996

9097
ts := time.Now().Format(time.RFC3339Nano)
91-
patchTarget.Annotations[AnnotationDigistKey] = newDigest
92-
patchTarget.Annotations["reconcile.fluxcd.io/forceAt"] = ts
93-
patchTarget.Annotations["reconcile.fluxcd.io/requestedAt"] = ts
98+
patchTargetHR.Annotations["reconcile.fluxcd.io/forceAt"] = ts
99+
patchTargetHR.Annotations["reconcile.fluxcd.io/requestedAt"] = ts
94100

95-
patch := client.MergeFrom(hr.DeepCopy())
101+
patchHR := client.MergeFrom(hr.DeepCopy())
102+
if err := r.Patch(ctx, patchTargetHR, patchHR); err != nil {
103+
logger.Error(err, "failed to patch HelmRelease", "name", patchTargetHR.Name)
104+
}
96105

97-
if err := r.Patch(ctx, patchTarget, patch); err != nil {
98-
logger.Error(err, "failed to patch HelmRelease", "name", patchTarget.Name)
106+
patchCM := client.MergeFrom(configMap.DeepCopy())
107+
patchTargetCM := configMap.DeepCopy()
108+
patchTargetCM.Annotations[HashAnnotation] = newDigest
109+
if err := r.Patch(ctx, patchTargetCM, patchCM); err != nil {
110+
logger.Error(err, "failed to patch HelmRelease", "name", patchTargetHR.Name)
99111
}
100112

101-
logger.Info("patched HelmRelease with new digest", "name", patchTarget.Name, "digest", newDigest, "version", hr.Status.History[0].Version)
113+
logger.Info("patched HelmRelease with new digest",
114+
"name", patchTargetHR.Name,
115+
"digest", newDigest,
116+
"version", hr.Status.History[0].Version)
102117

103118
return ctrl.Result{}, nil
104119
}
@@ -116,13 +131,19 @@ func (r *ConfigMapReconciler) SetupWithManager(
116131

117132
return isValidConfigMap(configMap)
118133
},
134+
119135
UpdateFunc: func(e event.UpdateEvent) bool {
120-
configMap, ok := e.ObjectNew.(*corev1.ConfigMap)
121-
if !ok {
136+
configMapOld, okOld := e.ObjectOld.(*corev1.ConfigMap)
137+
configMapNew, okNew := e.ObjectNew.(*corev1.ConfigMap)
138+
139+
if !okOld || !okNew || !isValidConfigMap(configMapNew) {
122140
return false
123141
}
124142

125-
return isValidConfigMap(configMap)
143+
oldHash := getDataHashCM(configMapOld.Data)
144+
newHash := getDataHashCM(configMapNew.Data)
145+
146+
return oldHash != newHash
126147
},
127148
DeleteFunc: func(e event.DeleteEvent) bool {
128149
return false
@@ -141,25 +162,8 @@ func (r *ConfigMapReconciler) SetupWithManager(
141162
}
142163

143164
func isValidConfigMap(cm *corev1.ConfigMap) bool {
144-
if v, ok := cm.Labels[LabelConfigMapReconcilerNameSourceKey]; ok {
165+
if v, ok := cm.Labels[LabelReconcilerNameSourceKey]; ok {
145166
return v == "true"
146167
}
147168
return false
148169
}
149-
150-
var (
151-
optionsInit sync.Once
152-
defaultOptions *controller.Options
153-
)
154-
155-
func ControllerOptions(maxConcurrency int, cacheSyncTimeout time.Duration) controller.Options {
156-
rateLimiters := workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](30*time.Second, 5*time.Minute)
157-
optionsInit.Do(func() {
158-
defaultOptions = &controller.Options{
159-
RateLimiter: rateLimiters,
160-
CacheSyncTimeout: cacheSyncTimeout,
161-
MaxConcurrentReconciles: maxConcurrency,
162-
}
163-
})
164-
return *defaultOptions
165-
}

0 commit comments

Comments
 (0)