Skip to content

Commit f067afe

Browse files
authored
First version of the controller command. (argoproj-labs#1127)
Signed-off-by: Denis Karpelevich <[email protected]>
1 parent 40823a5 commit f067afe

File tree

5 files changed

+210
-139
lines changed

5 files changed

+210
-139
lines changed

cmd/controller.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package main
2+
3+
import (
4+
"crypto/tls"
5+
6+
"github.com/argoproj-labs/argocd-image-updater/internal/controller"
7+
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
8+
"github.com/bombsimon/logrusr/v2"
9+
"github.com/spf13/cobra"
10+
ctrl "sigs.k8s.io/controller-runtime"
11+
"sigs.k8s.io/controller-runtime/pkg/healthz"
12+
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
13+
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
14+
"sigs.k8s.io/controller-runtime/pkg/webhook"
15+
"time"
16+
)
17+
18+
// newControllerCommand implements "controller" command
19+
func newControllerCommand() *cobra.Command {
20+
var metricsAddr string
21+
var enableLeaderElection bool
22+
var probeAddr string
23+
var secureMetrics bool
24+
var enableHTTP2 bool
25+
var LogLevel string
26+
var Interval time.Duration
27+
28+
var controllerCmd = &cobra.Command{
29+
Use: "controller",
30+
Short: "Manages ArgoCD Image Updater Controller.",
31+
Long: `The 'controller' command starts the Kubernetes controller responsible for managing
32+
ImageUpdater Custom Resources (CRs).
33+
34+
This controller monitors ImageUpdater CRs and reconciles them by:
35+
- Checking for new container image versions from specified registries.
36+
- Applying updates to applications based on CR policies.
37+
- Updating the status of the ImageUpdater CRs.
38+
39+
It operates as a long-running manager process within the Kubernetes cluster.
40+
Flags can configure its metrics, health probes, and leader election.
41+
This enables a CRD-driven approach to automated image updates with Argo CD.
42+
`,
43+
RunE: func(cmd *cobra.Command, args []string) error {
44+
if err := log.SetLogLevel(LogLevel); err != nil {
45+
return err
46+
}
47+
logrLogger := logrusr.New(log.Log()) // log.Log() should return the *logrus.Logger
48+
ctrl.SetLogger(logrLogger)
49+
setupLog := ctrl.Log.WithName("controller-setup")
50+
setupLog.Info("Controller runtime logger initialized.", "setAppLogLevel", LogLevel)
51+
52+
// if the enable-http2 flag is false (the default), http/2 should be disabled
53+
// due to its vulnerabilities. More specifically, disabling http/2 will
54+
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
55+
// Rapid Reset CVEs. For more information see:
56+
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
57+
// - https://github.com/advisories/GHSA-4374-p667-p6c8
58+
var tlsOpts []func(*tls.Config)
59+
disableHTTP2 := func(c *tls.Config) {
60+
setupLog.Info("disabling http/2")
61+
c.NextProtos = []string{"http/1.1"}
62+
}
63+
64+
if !enableHTTP2 {
65+
tlsOpts = append(tlsOpts, disableHTTP2)
66+
}
67+
68+
webhookServer := webhook.NewServer(webhook.Options{
69+
TLSOpts: tlsOpts,
70+
})
71+
72+
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
73+
// More info:
74+
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/server
75+
// - https://book.kubebuilder.io/reference/metrics.html
76+
metricsServerOptions := metricsserver.Options{
77+
BindAddress: metricsAddr,
78+
SecureServing: secureMetrics,
79+
// TODO(user): TLSOpts is used to allow configuring the TLS config used for the server. If certificates are
80+
// not provided, self-signed certificates will be generated by default. This option is not recommended for
81+
// production environments as self-signed certificates do not offer the same level of trust and security
82+
// as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing
83+
// unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName
84+
// to provide certificates, ensuring the server communicates using trusted and secure certificates.
85+
TLSOpts: tlsOpts,
86+
}
87+
88+
if secureMetrics {
89+
// FilterProvider is used to protect the metrics endpoint with authn/authz.
90+
// These configurations ensure that only authorized users and service accounts
91+
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
92+
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/filters#WithAuthenticationAndAuthorization
93+
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
94+
}
95+
96+
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
97+
Scheme: scheme,
98+
Metrics: metricsServerOptions,
99+
WebhookServer: webhookServer,
100+
HealthProbeBindAddress: probeAddr,
101+
LeaderElection: enableLeaderElection,
102+
LeaderElectionID: "c21b75f2.argoproj.io",
103+
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
104+
// when the Manager ends. This requires the binary to immediately end when the
105+
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
106+
// speeds up voluntary leader transitions as the new leader don't have to wait
107+
// LeaseDuration time first.
108+
//
109+
// In the default scaffold provided, the program ends immediately after
110+
// the manager stops, so would be fine to enable this option. However,
111+
// if you are doing or is intended to do any operation such as perform cleanups
112+
// after the manager stops then its usage might be unsafe.
113+
// LeaderElectionReleaseOnCancel: true,
114+
})
115+
if err != nil {
116+
setupLog.Error(err, "unable to start manager")
117+
return err
118+
}
119+
120+
reconcilerLogger := ctrl.Log.WithName("reconciler").WithName("ImageUpdater")
121+
if err = (&controller.ImageUpdaterReconciler{
122+
Client: mgr.GetClient(),
123+
Scheme: mgr.GetScheme(),
124+
Interval: Interval,
125+
Log: reconcilerLogger,
126+
}).SetupWithManager(mgr); err != nil {
127+
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdater")
128+
return err
129+
}
130+
// +kubebuilder:scaffold:builder
131+
132+
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
133+
setupLog.Error(err, "unable to set up health check")
134+
return err
135+
}
136+
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
137+
setupLog.Error(err, "unable to set up ready check")
138+
return err
139+
}
140+
141+
setupLog.Info("starting manager")
142+
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
143+
setupLog.Error(err, "problem running manager")
144+
return err
145+
}
146+
return nil
147+
},
148+
}
149+
controllerCmd.Flags().StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
150+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
151+
controllerCmd.Flags().StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
152+
controllerCmd.Flags().BoolVar(&enableLeaderElection, "leader-elect", false,
153+
"Enable leader election for controller manager. "+
154+
"Enabling this will ensure there is only one active controller manager.")
155+
controllerCmd.Flags().BoolVar(&secureMetrics, "metrics-secure", true,
156+
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
157+
controllerCmd.Flags().BoolVar(&enableHTTP2, "enable-http2", false,
158+
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
159+
controllerCmd.Flags().StringVar(&LogLevel, "loglevel", "info",
160+
"set the loglevel to one of trace|debug|info|warn|error")
161+
controllerCmd.Flags().DurationVar(&Interval, "interval", 2*time.Minute,
162+
"interval for how often to check for updates")
163+
164+
return controllerCmd
165+
}

cmd/controller_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
// TestNewControllerCommand tests various flags and their default values.
9+
func TestNewControllerCommand(t *testing.T) {
10+
asser := assert.New(t)
11+
controllerCommand := newControllerCommand()
12+
asser.Contains(controllerCommand.Use, "controller")
13+
asser.Equal(controllerCommand.Short, "Manages ArgoCD Image Updater Controller.")
14+
asser.Greater(len(controllerCommand.Long), 100)
15+
asser.NotNil(controllerCommand.RunE)
16+
asser.Equal("0", controllerCommand.Flag("metrics-bind-address").Value.String())
17+
asser.Equal(":8081", controllerCommand.Flag("health-probe-bind-address").Value.String())
18+
asser.Equal("false", controllerCommand.Flag("leader-elect").Value.String())
19+
asser.Equal("true", controllerCommand.Flag("metrics-secure").Value.String())
20+
asser.Equal("false", controllerCommand.Flag("enable-http2").Value.String())
21+
asser.Equal("2m0s", controllerCommand.Flag("interval").Value.String())
22+
asser.Equal("info", controllerCommand.Flag("loglevel").Value.String())
23+
asser.Nil(controllerCommand.Help())
24+
asser.True(controllerCommand.HasFlags())
25+
asser.True(controllerCommand.HasLocalFlags())
26+
asser.False(controllerCommand.HasSubCommands())
27+
asser.False(controllerCommand.HasHelpSubCommands())
28+
}

cmd/main.go

Lines changed: 5 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"k8s.io/apimachinery/pkg/runtime"
2021
"os"
2122
"text/template"
2223
"time"
@@ -27,32 +28,19 @@ import (
2728

2829
"github.com/spf13/cobra"
2930

30-
"crypto/tls"
31-
"flag"
32-
3331
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
3432
// to ensure that exec-entrypoint and run can make use of them.
3533
_ "k8s.io/client-go/plugin/pkg/client/auth"
3634

37-
"k8s.io/apimachinery/pkg/runtime"
35+
argocdimageupdaterv1alpha1 "github.com/argoproj-labs/argocd-image-updater/api/v1alpha1"
3836
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3937
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
40-
ctrl "sigs.k8s.io/controller-runtime"
41-
"sigs.k8s.io/controller-runtime/pkg/healthz"
42-
"sigs.k8s.io/controller-runtime/pkg/log/zap"
43-
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
44-
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
45-
"sigs.k8s.io/controller-runtime/pkg/webhook"
46-
47-
argocdimageupdaterv1alpha1 "github.com/argoproj-labs/argocd-image-updater/api/v1alpha1"
48-
"github.com/argoproj-labs/argocd-image-updater/internal/controller"
4938
// +kubebuilder:scaffold:imports
5039
)
5140

5241
var (
53-
lastRun time.Time
54-
scheme = runtime.NewScheme()
55-
setupLog = ctrl.Log.WithName("setup")
42+
lastRun time.Time
43+
scheme = runtime.NewScheme()
5644
)
5745

5846
// Default ArgoCD server address when running in same cluster as ArgoCD
@@ -103,6 +91,7 @@ func newRootCommand() error {
10391
rootCmd.AddCommand(newVersionCommand())
10492
rootCmd.AddCommand(newTestCommand())
10593
rootCmd.AddCommand(newTemplateCommand())
94+
rootCmd.AddCommand(newControllerCommand())
10695
err := rootCmd.Execute()
10796
return err
10897
}
@@ -133,120 +122,5 @@ func main() {
133122
os.Exit(1)
134123
}
135124

136-
var metricsAddr string
137-
var enableLeaderElection bool
138-
var probeAddr string
139-
var secureMetrics bool
140-
var enableHTTP2 bool
141-
var tlsOpts []func(*tls.Config)
142-
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
143-
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
144-
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
145-
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
146-
"Enable leader election for controller manager. "+
147-
"Enabling this will ensure there is only one active controller manager.")
148-
flag.BoolVar(&secureMetrics, "metrics-secure", true,
149-
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
150-
flag.BoolVar(&enableHTTP2, "enable-http2", false,
151-
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
152-
opts := zap.Options{
153-
Development: true,
154-
}
155-
opts.BindFlags(flag.CommandLine)
156-
flag.Parse()
157-
158-
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
159-
160-
// if the enable-http2 flag is false (the default), http/2 should be disabled
161-
// due to its vulnerabilities. More specifically, disabling http/2 will
162-
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
163-
// Rapid Reset CVEs. For more information see:
164-
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
165-
// - https://github.com/advisories/GHSA-4374-p667-p6c8
166-
disableHTTP2 := func(c *tls.Config) {
167-
setupLog.Info("disabling http/2")
168-
c.NextProtos = []string{"http/1.1"}
169-
}
170-
171-
if !enableHTTP2 {
172-
tlsOpts = append(tlsOpts, disableHTTP2)
173-
}
174-
175-
webhookServer := webhook.NewServer(webhook.Options{
176-
TLSOpts: tlsOpts,
177-
})
178-
179-
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
180-
// More info:
181-
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/server
182-
// - https://book.kubebuilder.io/reference/metrics.html
183-
metricsServerOptions := metricsserver.Options{
184-
BindAddress: metricsAddr,
185-
SecureServing: secureMetrics,
186-
// TODO(user): TLSOpts is used to allow configuring the TLS config used for the server. If certificates are
187-
// not provided, self-signed certificates will be generated by default. This option is not recommended for
188-
// production environments as self-signed certificates do not offer the same level of trust and security
189-
// as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing
190-
// unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName
191-
// to provide certificates, ensuring the server communicates using trusted and secure certificates.
192-
TLSOpts: tlsOpts,
193-
}
194-
195-
if secureMetrics {
196-
// FilterProvider is used to protect the metrics endpoint with authn/authz.
197-
// These configurations ensure that only authorized users and service accounts
198-
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
199-
// https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/metrics/filters#WithAuthenticationAndAuthorization
200-
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
201-
}
202-
203-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
204-
Scheme: scheme,
205-
Metrics: metricsServerOptions,
206-
WebhookServer: webhookServer,
207-
HealthProbeBindAddress: probeAddr,
208-
LeaderElection: enableLeaderElection,
209-
LeaderElectionID: "c21b75f2.argoproj.io",
210-
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
211-
// when the Manager ends. This requires the binary to immediately end when the
212-
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
213-
// speeds up voluntary leader transitions as the new leader don't have to wait
214-
// LeaseDuration time first.
215-
//
216-
// In the default scaffold provided, the program ends immediately after
217-
// the manager stops, so would be fine to enable this option. However,
218-
// if you are doing or is intended to do any operation such as perform cleanups
219-
// after the manager stops then its usage might be unsafe.
220-
// LeaderElectionReleaseOnCancel: true,
221-
})
222-
if err != nil {
223-
setupLog.Error(err, "unable to start manager")
224-
os.Exit(1)
225-
}
226-
227-
if err = (&controller.ImageUpdaterReconciler{
228-
Client: mgr.GetClient(),
229-
Scheme: mgr.GetScheme(),
230-
}).SetupWithManager(mgr); err != nil {
231-
setupLog.Error(err, "unable to create controller", "controller", "ImageUpdater")
232-
os.Exit(1)
233-
}
234-
// +kubebuilder:scaffold:builder
235-
236-
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
237-
setupLog.Error(err, "unable to set up health check")
238-
os.Exit(1)
239-
}
240-
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
241-
setupLog.Error(err, "unable to set up ready check")
242-
os.Exit(1)
243-
}
244-
245-
setupLog.Info("starting manager")
246-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
247-
setupLog.Error(err, "problem running manager")
248-
os.Exit(1)
249-
}
250-
251125
os.Exit(0)
252126
}

internal/controller/imageupdater_controller.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ package controller
1818

1919
import (
2020
"context"
21-
21+
"github.com/go-logr/logr"
2222
"k8s.io/apimachinery/pkg/runtime"
2323
ctrl "sigs.k8s.io/controller-runtime"
2424
"sigs.k8s.io/controller-runtime/pkg/client"
25-
logf "sigs.k8s.io/controller-runtime/pkg/log"
2625
"time"
2726

2827
argocdimageupdaterv1alpha1 "github.com/argoproj-labs/argocd-image-updater/api/v1alpha1"
@@ -33,6 +32,7 @@ type ImageUpdaterReconciler struct {
3332
client.Client
3433
Scheme *runtime.Scheme
3534
Interval time.Duration
35+
Log logr.Logger
3636
}
3737

3838
// +kubebuilder:rbac:groups=argocd-image-updater.argoproj.io,resources=imageupdaters,verbs=get;list;watch;create;update;patch;delete
@@ -76,8 +76,7 @@ type ImageUpdaterReconciler struct {
7676
// For more details, check Reconcile and its Result here:
7777
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
7878
func (r *ImageUpdaterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
79-
log := logf.FromContext(ctx)
80-
log = log.WithValues("imageupdater", req.NamespacedName) // Add context to logs
79+
log := r.Log.WithValues("imageupdater", req.NamespacedName) // Add context to logs
8180
log.Info("Reconciling ImageUpdater")
8281

8382
// TODO: Implement the full reconciliation logic as described in the docstring:

0 commit comments

Comments
 (0)