From e39cff881db0f88203c12534f07e7b48f6037acf Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 15 Apr 2022 11:44:20 +0200 Subject: [PATCH] Migrate main.go --- .VERSION | 1 + controllers/onepassworditem_controller.go | 22 +-- main.go | 164 +++++++++++++++++++++- pkg/utils/k8sutil.go | 45 ++++++ version/version.go | 6 + 5 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 .VERSION create mode 100644 pkg/utils/k8sutil.go create mode 100644 version/version.go diff --git a/.VERSION b/.VERSION new file mode 100644 index 0000000..e21e727 --- /dev/null +++ b/.VERSION @@ -0,0 +1 @@ +1.4.0 \ No newline at end of file diff --git a/controllers/onepassworditem_controller.go b/controllers/onepassworditem_controller.go index d73ecdc..f19b556 100644 --- a/controllers/onepassworditem_controller.go +++ b/controllers/onepassworditem_controller.go @@ -49,9 +49,9 @@ var log = logf.Log.WithName("controller_onepassworditem") // OnePasswordItemReconciler reconciles a OnePasswordItem object type OnePasswordItemReconciler struct { - kubeClient kubeClient.Client - scheme *runtime.Scheme - opConnectClient connect.Client + Client kubeClient.Client + Scheme *runtime.Scheme + OpConnectClient connect.Client } //+kubebuilder:rbac:groups=onepassword.onepassword.com,resources=onepassworditems,verbs=get;list;watch;create;update;patch;delete @@ -72,7 +72,7 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, request ctrl. reqLogger.Info("Reconciling OnePasswordItem") onepassworditem := &onepasswordv1.OnePasswordItem{} - err := r.kubeClient.Get(context.Background(), request.NamespacedName, onepassworditem) + err := r.Client.Get(context.Background(), request.NamespacedName, onepassworditem) if err != nil { if errors.IsNotFound(err) { return reconcile.Result{}, nil @@ -86,7 +86,7 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, request ctrl. // This is so we can handle cleanup of associated secrets properly if !utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) { onepassworditem.ObjectMeta.Finalizers = append(onepassworditem.ObjectMeta.Finalizers, finalizer) - if err := r.kubeClient.Update(context.Background(), onepassworditem); err != nil { + if err := r.Client.Update(context.Background(), onepassworditem); err != nil { return reconcile.Result{}, err } } @@ -135,7 +135,7 @@ func (r *OnePasswordItemReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *OnePasswordItemReconciler) removeFinalizer(onePasswordItem *onepasswordv1.OnePasswordItem) error { onePasswordItem.ObjectMeta.Finalizers = utils.RemoveString(onePasswordItem.ObjectMeta.Finalizers, finalizer) - if err := r.kubeClient.Update(context.Background(), onePasswordItem); err != nil { + if err := r.Client.Update(context.Background(), onePasswordItem); err != nil { return err } return nil @@ -146,8 +146,8 @@ func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(onePasswordItem *one kubernetesSecret.ObjectMeta.Name = onePasswordItem.Name kubernetesSecret.ObjectMeta.Namespace = onePasswordItem.Namespace - r.kubeClient.Delete(context.Background(), kubernetesSecret) - if err := r.kubeClient.Delete(context.Background(), kubernetesSecret); err != nil { + r.Client.Delete(context.Background(), kubernetesSecret) + if err := r.Client.Delete(context.Background(), kubernetesSecret); err != nil { if !errors.IsNotFound(err) { return err } @@ -162,13 +162,13 @@ func (r *OnePasswordItemReconciler) HandleOnePasswordItem(resource *onepasswordv secretType := resource.Type autoRestart := annotations[op.RestartDeploymentsAnnotation] - item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) + item, err := onepassword.GetOnePasswordItemByPath(r.OpConnectClient, resource.Spec.ItemPath) if err != nil { return fmt.Errorf("Failed to retrieve item: %v", err) } // Create owner reference. - gvk, err := apiutil.GVKForObject(resource, r.scheme) + gvk, err := apiutil.GVKForObject(resource, r.Scheme) if err != nil { return fmt.Errorf("could not to retrieve group version kind: %v", err) } @@ -179,5 +179,5 @@ func (r *OnePasswordItemReconciler) HandleOnePasswordItem(resource *onepasswordv UID: resource.GetUID(), } - return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations, ownerRef) + return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations, ownerRef) } diff --git a/main.go b/main.go index 57fdba7..2056825 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,29 @@ limitations under the License. package main import ( + "errors" "flag" + "fmt" "os" + "runtime" + "strconv" + "strings" + "time" + + "github.com/1Password/connect-sdk-go/connect" + op "github.com/1Password/onepassword-operator/pkg/onepassword" + "github.com/1Password/onepassword-operator/pkg/utils" + "github.com/1Password/onepassword-operator/version" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" + + // sdkVersion "github.com/operator-framework/operator-sdk/version" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" + k8sruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -37,8 +52,9 @@ import ( ) var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + scheme = k8sruntime.NewScheme() + setupLog = ctrl.Log.WithName("setup") + WatchNamespaceEnvVar = "WATCH_NAMESPACE" ) func init() { @@ -48,6 +64,14 @@ func init() { //+kubebuilder:scaffold:scheme } +func printVersion() { + setupLog.Info(fmt.Sprintf("Operator Version: %s", version.Version)) + setupLog.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + // TODO figure out how to get operator-sdk version + // setupLog.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version)) +} + func main() { var metricsAddr string var enableLeaderElection bool @@ -65,22 +89,45 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + printVersion() + + namespace := os.Getenv(WatchNamespaceEnvVar) + + options := ctrl.Options{ Scheme: scheme, + Namespace: namespace, MetricsBindAddress: metricsAddr, Port: 9443, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "c26807fd.onepassword.com", - }) + } + + // Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2) + // Note that this is not intended to be used for excluding namespaces, this is better done via a Predicate + // Also note that you may face performance issues when using this with a high number of namespaces. + if strings.Contains(namespace, ",") { + options.Namespace = "" + options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(namespace, ",")) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } + // Setup One Password Client + opConnectClient, err := connect.NewClientFromEnvironment() + if err != nil { + setupLog.Error(err, "failed to create 1Password client") + os.Exit(1) + } + if err = (&controllers.OnePasswordItemReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + OpConnectClient: opConnectClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OnePasswordItem") os.Exit(1) @@ -101,4 +148,107 @@ func main() { setupLog.Error(err, "problem running manager") os.Exit(1) } + + deploymentNamespace, err := utils.GetOperatorNamespace() + if err != nil { + setupLog.Error(err, "Failed to get namespace") + os.Exit(1) + } + + //Setup 1PasswordConnect + if shouldManageConnect() { + setupLog.Info("Automated Connect Management Enabled") + go func() { + connectStarted := false + for !connectStarted { + err := op.SetupConnect(mgr.GetClient(), deploymentNamespace) + // Cache Not Started is an acceptable error. Retry until cache is started. + if err != nil && !errors.Is(err, &cache.ErrCacheNotStarted{}) { + setupLog.Error(err, "") + os.Exit(1) + } + if err == nil { + connectStarted = true + } + } + }() + } else { + setupLog.Info("Automated Connect Management Disabled") + } + + // TODO: Configure Metrics Service. See: https://sdk.operatorframework.io/docs/building-operators/golang/migration/#export-metrics + + // Setup update secrets task + updatedSecretsPoller := op.NewManager(mgr.GetClient(), opConnectClient, shouldAutoRestartDeployments()) + done := make(chan bool) + ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets()) + go func() { + for { + select { + case <-done: + ticker.Stop() + return + case <-ticker.C: + err := updatedSecretsPoller.UpdateKubernetesSecretsTask() + if err != nil { + setupLog.Error(err, "error running update kubernetes secret task") + } + } + } + }() + + // Start the Cmd + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + setupLog.Error(err, "Manager exited non-zero") + done <- true + os.Exit(1) + } + +} + +const manageConnect = "MANAGE_CONNECT" + +func shouldManageConnect() bool { + shouldManageConnect, found := os.LookupEnv(manageConnect) + if found { + shouldManageConnectBool, err := strconv.ParseBool(strings.ToLower(shouldManageConnect)) + if err != nil { + setupLog.Error(err, "") + os.Exit(1) + } + return shouldManageConnectBool + } + return false +} + +const envPollingIntervalVariable = "POLLING_INTERVAL" +const defaultPollingInterval = 600 + +func getPollingIntervalForUpdatingSecrets() time.Duration { + timeInSecondsString, found := os.LookupEnv(envPollingIntervalVariable) + if found { + timeInSeconds, err := strconv.Atoi(timeInSecondsString) + if err == nil { + return time.Duration(timeInSeconds) * time.Second + } + setupLog.Info("Invalid value set for polling interval. Must be a valid integer.") + } + + setupLog.Info(fmt.Sprintf("Using default polling interval of %v seconds", defaultPollingInterval)) + return time.Duration(defaultPollingInterval) * time.Second +} + +const restartDeploymentsEnvVariable = "AUTO_RESTART" + +func shouldAutoRestartDeployments() bool { + shouldAutoRestartDeployments, found := os.LookupEnv(restartDeploymentsEnvVariable) + if found { + shouldAutoRestartDeploymentsBool, err := strconv.ParseBool(strings.ToLower(shouldAutoRestartDeployments)) + if err != nil { + setupLog.Error(err, "") + os.Exit(1) + } + return shouldAutoRestartDeploymentsBool + } + return false } diff --git a/pkg/utils/k8sutil.go b/pkg/utils/k8sutil.go new file mode 100644 index 0000000..aea170f --- /dev/null +++ b/pkg/utils/k8sutil.go @@ -0,0 +1,45 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "os" + "strings" +) + +var ForceRunModeEnv = "OSDK_FORCE_RUN_MODE" + +type RunModeType string + +const ( + LocalRunMode RunModeType = "local" + ClusterRunMode RunModeType = "cluster" +) + +// ErrNoNamespace indicates that a namespace could not be found for the current +// environment +var ErrNoNamespace = fmt.Errorf("namespace not found for current environment") + +// ErrRunLocal indicates that the operator is set to run in local mode (this error +// is returned by functions that only work on operators running in cluster mode) +var ErrRunLocal = fmt.Errorf("operator run mode forced to local") + +// GetOperatorNamespace returns the namespace the operator should be running in. +func GetOperatorNamespace() (string, error) { + if isRunModeLocal() { + return "", ErrRunLocal + } + nsBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + if os.IsNotExist(err) { + return "", ErrNoNamespace + } + return "", err + } + ns := strings.TrimSpace(string(nsBytes)) + return ns, nil +} + +func isRunModeLocal() bool { + return os.Getenv(ForceRunModeEnv) == string(LocalRunMode) +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..7eada5e --- /dev/null +++ b/version/version.go @@ -0,0 +1,6 @@ +package version + +// TODO figure out if this package makes sense. +var ( + Version = "0.0.1" +)