Skip to content

Commit 05415ef

Browse files
authored
✨ Migrate operator-controller cli handling to cobra (#1717)
* migrate operator-controller command handling to cobra Signed-off-by: Omar Farag <[email protected]> * remove deprecated version flag handling Signed-off-by: Omar Farag <[email protected]> * fix error handling, fix lint issues and tidy go.mod Signed-off-by: Omar Farag <[email protected]> --------- Signed-off-by: Omar Farag <[email protected]>
1 parent 00251d6 commit 05415ef

File tree

2 files changed

+116
-83
lines changed

2 files changed

+116
-83
lines changed

cmd/operator-controller/main.go

Lines changed: 115 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030

3131
"github.com/containers/image/v5/types"
3232
"github.com/sirupsen/logrus"
33-
"github.com/spf13/pflag"
33+
"github.com/spf13/cobra"
3434
corev1 "k8s.io/api/core/v1"
3535
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
3636
"k8s.io/apimachinery/pkg/fields"
@@ -78,8 +78,22 @@ var (
7878
setupLog = ctrl.Log.WithName("setup")
7979
defaultSystemNamespace = "olmv1-system"
8080
certWatcher *certwatcher.CertWatcher
81+
cfg = &config{}
8182
)
8283

84+
type config struct {
85+
metricsAddr string
86+
certFile string
87+
keyFile string
88+
enableLeaderElection bool
89+
probeAddr string
90+
cachePath string
91+
systemNamespace string
92+
catalogdCasDir string
93+
pullCasDir string
94+
globalPullSecret string
95+
}
96+
8397
const authFilePrefix = "operator-controller-global-pull-secrets"
8498

8599
// podNamespace checks whether the controller is running in a Pod vs.
@@ -94,83 +108,94 @@ func podNamespace() string {
94108
return string(namespace)
95109
}
96110

97-
func main() {
98-
var (
99-
metricsAddr string
100-
certFile string
101-
keyFile string
102-
enableLeaderElection bool
103-
probeAddr string
104-
cachePath string
105-
operatorControllerVersion bool
106-
systemNamespace string
107-
catalogdCasDir string
108-
pullCasDir string
109-
globalPullSecret string
110-
)
111-
flag.StringVar(&metricsAddr, "metrics-bind-address", "", "The address for the metrics endpoint. Requires tls-cert and tls-key. (Default: ':8443')")
112-
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
113-
flag.StringVar(&catalogdCasDir, "catalogd-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to the Catalogd web service.")
114-
flag.StringVar(&pullCasDir, "pull-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to image registries.")
115-
flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for the metrics server. Required to enable the metrics server. Requires tls-key.")
116-
flag.StringVar(&keyFile, "tls-key", "", "The key file used for the metrics server. Required to enable the metrics server. Requires tls-cert")
117-
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
111+
var operatorControllerCmd = &cobra.Command{
112+
Use: "operator-controller",
113+
Short: "operator-controller is the central component of Operator Lifecycle Manager (OLM) v1",
114+
RunE: func(cmd *cobra.Command, args []string) error {
115+
if err := validateMetricsFlags(); err != nil {
116+
return err
117+
}
118+
return run()
119+
},
120+
}
121+
122+
var versionCommand = &cobra.Command{
123+
Use: "version",
124+
Short: "Prints operator-controller version information",
125+
Run: func(cmd *cobra.Command, args []string) {
126+
fmt.Println(version.String())
127+
},
128+
}
129+
130+
func init() {
131+
//create flagset, the collection of flags for this command
132+
flags := operatorControllerCmd.Flags()
133+
flags.StringVar(&cfg.metricsAddr, "metrics-bind-address", "", "The address for the metrics endpoint. Requires tls-cert and tls-key. (Default: ':8443')")
134+
flags.StringVar(&cfg.probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
135+
flags.StringVar(&cfg.catalogdCasDir, "catalogd-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to the Catalogd web service.")
136+
flags.StringVar(&cfg.pullCasDir, "pull-cas-dir", "", "The directory of TLS certificate authorities to use for verifying HTTPS connections to image registries.")
137+
flags.StringVar(&cfg.certFile, "tls-cert", "", "The certificate file used for the metrics server. Required to enable the metrics server. Requires tls-key.")
138+
flags.StringVar(&cfg.keyFile, "tls-key", "", "The key file used for the metrics server. Required to enable the metrics server. Requires tls-cert")
139+
flags.BoolVar(&cfg.enableLeaderElection, "leader-elect", false,
118140
"Enable leader election for controller manager. "+
119141
"Enabling this will ensure there is only one active controller manager.")
120-
flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
121-
flag.BoolVar(&operatorControllerVersion, "version", false, "Prints operator-controller version information")
122-
flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.")
123-
flag.StringVar(&globalPullSecret, "global-pull-secret", "", "The <namespace>/<name> of the global pull secret that is going to be used to pull bundle images.")
142+
flags.StringVar(&cfg.cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
143+
flags.StringVar(&cfg.systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.")
144+
flags.StringVar(&cfg.globalPullSecret, "global-pull-secret", "", "The <namespace>/<name> of the global pull secret that is going to be used to pull bundle images.")
145+
146+
//adds version sub command
147+
operatorControllerCmd.AddCommand(versionCommand)
124148

125149
klog.InitFlags(flag.CommandLine)
126150
if klog.V(4).Enabled() {
127151
logrus.SetLevel(logrus.DebugLevel)
128152
}
129153

130-
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
131-
features.OperatorControllerFeatureGate.AddFlag(pflag.CommandLine)
132-
pflag.Parse()
154+
//add klog flags to flagset
155+
flags.AddGoFlagSet(flag.CommandLine)
133156

134-
if operatorControllerVersion {
135-
fmt.Println(version.String())
136-
os.Exit(0)
137-
}
138-
139-
if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
157+
//add feature gate flags to flagset
158+
features.OperatorControllerFeatureGate.AddFlag(flags)
159+
}
160+
func validateMetricsFlags() error {
161+
if (cfg.certFile != "" && cfg.keyFile == "") || (cfg.certFile == "" && cfg.keyFile != "") {
140162
setupLog.Error(errors.New("missing TLS configuration"),
141163
"tls-cert and tls-key flags must be used together",
142-
"certFile", certFile, "keyFile", keyFile)
143-
os.Exit(1)
164+
"certFile", cfg.certFile, "keyFile", cfg.keyFile)
165+
return fmt.Errorf("unable to configure TLS certificates: tls-cert and tls-key flags must be used together")
144166
}
145167

146-
if metricsAddr != "" && certFile == "" && keyFile == "" {
168+
if cfg.metricsAddr != "" && cfg.certFile == "" && cfg.keyFile == "" {
147169
setupLog.Error(errors.New("invalid metrics configuration"),
148170
"metrics-bind-address requires tls-cert and tls-key flags to be set",
149-
"metricsAddr", metricsAddr, "certFile", certFile, "keyFile", keyFile)
150-
os.Exit(1)
171+
"metricsAddr", cfg.metricsAddr, "certFile", cfg.certFile, "keyFile", cfg.keyFile)
172+
return fmt.Errorf("metrics-bind-address requires tls-cert and tls-key flags to be set")
151173
}
152174

153-
if certFile != "" && keyFile != "" && metricsAddr == "" {
154-
metricsAddr = ":8443"
175+
if cfg.certFile != "" && cfg.keyFile != "" && cfg.metricsAddr == "" {
176+
cfg.metricsAddr = ":8443"
155177
}
156-
178+
return nil
179+
}
180+
func run() error {
157181
ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig()))
158182

159183
setupLog.Info("starting up the controller", "version info", version.String())
160184

161185
authFilePath := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.json", authFilePrefix, apimachineryrand.String(8)))
162186
var globalPullSecretKey *k8stypes.NamespacedName
163-
if globalPullSecret != "" {
164-
secretParts := strings.Split(globalPullSecret, "/")
187+
if cfg.globalPullSecret != "" {
188+
secretParts := strings.Split(cfg.globalPullSecret, "/")
165189
if len(secretParts) != 2 {
166-
setupLog.Error(fmt.Errorf("incorrect number of components"), "value of global-pull-secret should be of the format <namespace>/<name>")
167-
os.Exit(1)
190+
err := fmt.Errorf("incorrect number of components")
191+
setupLog.Error(err, "value of global-pull-secret should be of the format <namespace>/<name>")
192+
return err
168193
}
169194
globalPullSecretKey = &k8stypes.NamespacedName{Name: secretParts[1], Namespace: secretParts[0]}
170195
}
171196

172-
if systemNamespace == "" {
173-
systemNamespace = podNamespace()
197+
if cfg.systemNamespace == "" {
198+
cfg.systemNamespace = podNamespace()
174199
}
175200

176201
setupLog.Info("set up manager")
@@ -180,7 +205,7 @@ func main() {
180205
&catalogd.ClusterCatalog{}: {Label: k8slabels.Everything()},
181206
},
182207
DefaultNamespaces: map[string]crcache.Config{
183-
systemNamespace: {LabelSelector: k8slabels.Everything()},
208+
cfg.systemNamespace: {LabelSelector: k8slabels.Everything()},
184209
},
185210
DefaultLabelSelector: k8slabels.Nothing(),
186211
}
@@ -198,19 +223,19 @@ func main() {
198223
}
199224

200225
metricsServerOptions := server.Options{}
201-
if len(certFile) > 0 && len(keyFile) > 0 {
202-
setupLog.Info("Starting metrics server with TLS enabled", "addr", metricsAddr, "tls-cert", certFile, "tls-key", keyFile)
226+
if len(cfg.certFile) > 0 && len(cfg.keyFile) > 0 {
227+
setupLog.Info("Starting metrics server with TLS enabled", "addr", cfg.metricsAddr, "tls-cert", cfg.certFile, "tls-key", cfg.keyFile)
203228

204-
metricsServerOptions.BindAddress = metricsAddr
229+
metricsServerOptions.BindAddress = cfg.metricsAddr
205230
metricsServerOptions.SecureServing = true
206231
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
207232

208233
// If the certificate files change, the watcher will reload them.
209234
var err error
210-
certWatcher, err = certwatcher.New(certFile, keyFile)
235+
certWatcher, err = certwatcher.New(cfg.certFile, cfg.keyFile)
211236
if err != nil {
212237
setupLog.Error(err, "Failed to initialize certificate watcher")
213-
os.Exit(1)
238+
return err
214239
}
215240

216241
metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) {
@@ -239,8 +264,8 @@ func main() {
239264
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
240265
Scheme: scheme.Scheme,
241266
Metrics: metricsServerOptions,
242-
HealthProbeBindAddress: probeAddr,
243-
LeaderElection: enableLeaderElection,
267+
HealthProbeBindAddress: cfg.probeAddr,
268+
LeaderElection: cfg.enableLeaderElection,
244269
LeaderElectionID: "9c4404e7.operatorframework.io",
245270
LeaderElectionReleaseOnCancel: true,
246271
// Recommended Leader Election values
@@ -264,19 +289,19 @@ func main() {
264289
})
265290
if err != nil {
266291
setupLog.Error(err, "unable to start manager")
267-
os.Exit(1)
292+
return err
268293
}
269294

270295
coreClient, err := corev1client.NewForConfig(mgr.GetConfig())
271296
if err != nil {
272297
setupLog.Error(err, "unable to create core client")
273-
os.Exit(1)
298+
return err
274299
}
275300
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
276301
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
277302

278303
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
279-
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), systemNamespace)),
304+
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),
280305
helmclient.ClientNamespaceMapper(func(obj client.Object) (string, error) {
281306
ext := obj.(*ocv1.ClusterExtension)
282307
return ext.Spec.Namespace, nil
@@ -285,42 +310,42 @@ func main() {
285310
)
286311
if err != nil {
287312
setupLog.Error(err, "unable to config for creating helm client")
288-
os.Exit(1)
313+
return err
289314
}
290315

291316
acg, err := action.NewWrappedActionClientGetter(cfgGetter,
292317
helmclient.WithFailureRollbacks(false),
293318
)
294319
if err != nil {
295320
setupLog.Error(err, "unable to create helm client")
296-
os.Exit(1)
321+
return err
297322
}
298323

299-
certPoolWatcher, err := httputil.NewCertPoolWatcher(catalogdCasDir, ctrl.Log.WithName("cert-pool"))
324+
certPoolWatcher, err := httputil.NewCertPoolWatcher(cfg.catalogdCasDir, ctrl.Log.WithName("cert-pool"))
300325
if err != nil {
301326
setupLog.Error(err, "unable to create CA certificate pool")
302-
os.Exit(1)
327+
return err
303328
}
304329

305330
if certWatcher != nil {
306331
setupLog.Info("Adding certificate watcher to manager")
307332
if err := mgr.Add(certWatcher); err != nil {
308333
setupLog.Error(err, "unable to add certificate watcher to manager")
309-
os.Exit(1)
334+
return err
310335
}
311336
}
312337

313-
if err := fsutil.EnsureEmptyDirectory(cachePath, 0700); err != nil {
338+
if err := fsutil.EnsureEmptyDirectory(cfg.cachePath, 0700); err != nil {
314339
setupLog.Error(err, "unable to ensure empty cache directory")
315-
os.Exit(1)
340+
return err
316341
}
317342

318-
imageCache := imageutil.BundleCache(filepath.Join(cachePath, "unpack"))
343+
imageCache := imageutil.BundleCache(filepath.Join(cfg.cachePath, "unpack"))
319344
imagePuller := &imageutil.ContainersImagePuller{
320345
SourceCtxFunc: func(ctx context.Context) (*types.SystemContext, error) {
321346
srcContext := &types.SystemContext{
322-
DockerCertPath: pullCasDir,
323-
OCICertPath: pullCasDir,
347+
DockerCertPath: cfg.pullCasDir,
348+
OCICertPath: cfg.pullCasDir,
324349
}
325350
logger := log.FromContext(ctx)
326351
if _, err := os.Stat(authFilePath); err == nil && globalPullSecretKey != nil {
@@ -340,15 +365,15 @@ func main() {
340365
return crfinalizer.Result{}, imageCache.Delete(ctx, obj.GetName())
341366
})); err != nil {
342367
setupLog.Error(err, "unable to register finalizer", "finalizerKey", controllers.ClusterExtensionCleanupUnpackCacheFinalizer)
343-
os.Exit(1)
368+
return err
344369
}
345370

346371
cl := mgr.GetClient()
347372

348-
catalogsCachePath := filepath.Join(cachePath, "catalogs")
373+
catalogsCachePath := filepath.Join(cfg.cachePath, "catalogs")
349374
if err := os.MkdirAll(catalogsCachePath, 0700); err != nil {
350375
setupLog.Error(err, "unable to create catalogs cache directory")
351-
os.Exit(1)
376+
return err
352377
}
353378
catalogClientBackend := cache.NewFilesystemCache(catalogsCachePath)
354379
catalogClient := catalogclient.New(catalogClientBackend, func() (*http.Client, error) {
@@ -374,7 +399,7 @@ func main() {
374399
aeClient, err := apiextensionsv1client.NewForConfig(mgr.GetConfig())
375400
if err != nil {
376401
setupLog.Error(err, "unable to create apiextensions client")
377-
os.Exit(1)
402+
return err
378403
}
379404

380405
preflights := []applier.Preflight{
@@ -394,7 +419,7 @@ func main() {
394419
}))
395420
if err != nil {
396421
setupLog.Error(err, "unable to register content manager cleanup finalizer")
397-
os.Exit(1)
422+
return err
398423
}
399424

400425
if err = (&controllers.ClusterExtensionReconciler{
@@ -408,7 +433,7 @@ func main() {
408433
Manager: cm,
409434
}).SetupWithManager(mgr); err != nil {
410435
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
411-
os.Exit(1)
436+
return err
412437
}
413438

414439
if err = (&controllers.ClusterCatalogReconciler{
@@ -417,41 +442,49 @@ func main() {
417442
CatalogCachePopulator: catalogClient,
418443
}).SetupWithManager(mgr); err != nil {
419444
setupLog.Error(err, "unable to create controller", "controller", "ClusterCatalog")
420-
os.Exit(1)
445+
return err
421446
}
422447

423448
if globalPullSecretKey != nil {
424-
setupLog.Info("creating SecretSyncer controller for watching secret", "Secret", globalPullSecret)
449+
setupLog.Info("creating SecretSyncer controller for watching secret", "Secret", cfg.globalPullSecret)
425450
err := (&controllers.PullSecretReconciler{
426451
Client: mgr.GetClient(),
427452
AuthFilePath: authFilePath,
428453
SecretKey: *globalPullSecretKey,
429454
}).SetupWithManager(mgr)
430455
if err != nil {
431456
setupLog.Error(err, "unable to create controller", "controller", "SecretSyncer")
432-
os.Exit(1)
457+
return err
433458
}
434459
}
435460

436461
//+kubebuilder:scaffold:builder
437462

438463
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
439464
setupLog.Error(err, "unable to set up health check")
440-
os.Exit(1)
465+
return err
441466
}
442467
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
443468
setupLog.Error(err, "unable to set up ready check")
444-
os.Exit(1)
469+
return err
445470
}
446471

447472
setupLog.Info("starting manager")
448473
ctx := ctrl.SetupSignalHandler()
449474
if err := mgr.Start(ctx); err != nil {
450475
setupLog.Error(err, "problem running manager")
451-
os.Exit(1)
476+
return err
452477
}
453478
if err := os.Remove(authFilePath); err != nil {
454479
setupLog.Error(err, "failed to cleanup temporary auth file")
480+
return err
481+
}
482+
return nil
483+
}
484+
485+
func main() {
486+
if err := operatorControllerCmd.Execute(); err != nil {
487+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
455488
os.Exit(1)
456489
}
457490
}

0 commit comments

Comments
 (0)