### Installing CRDs and Running memcached-operator Controller (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This combined `make` command first installs the Custom Resource Definitions (CRDs) and then immediately runs the memcached-operator controller locally. It's a convenient shortcut for setting up and starting the operator for testing. ```Shell make install run ``` -------------------------------- ### Getting Operator SDK CLI Binaries Locally (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This command uses Dagger to download and export the necessary Operator SDK CLI binaries to the local `./bin` directory, making them available for local development and testing. ```bash dagger call -m operator-sdk --src . sdk get-cli export --path ./bin ``` -------------------------------- ### Setting up and Running Kubernetes Controller Manager in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This Go snippet initializes the Kubernetes client scheme, parses command-line arguments for metrics, probes, and leader election, creates a `controller-runtime` manager, instantiates and configures a `MemcachedReconciler` with the manager's client and event recorder, and adds health and readiness checks before starting the manager. It serves as the entry point for a Kubernetes operator. ```golang /* Copyright 2023. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "flag" "os" // 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" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" cachev1alpha1 "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/controllers" "github.com/sirupsen/logrus" //+kubebuilder:scaffold:imports ) var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(cachev1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{ BindAddress: metricsAddr, }, WebhookServer: webhook.NewServer(webhook.Options{ Port: 9443, }), HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "1858d68a.example.com", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly // speeds up voluntary leader transitions as the new leader don't have to wait // LeaseDuration time first. // // In the default scaffold provided, the program ends immediately after // the manager stops, so would be fine to enable this option. However, // if you are doing or is intended to do any operation such as perform cleanups // after the manager stops then its usage might be unsafe. // LeaderElectionReleaseOnCancel: true, }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } log := logrus.New() log.SetLevel(logrus.DebugLevel) memcachedReconciler := controllers.NewMemcachedReconciler( mgr.GetClient(), logrus.NewEntry(log), mgr.GetEventRecorderFor("memcached-controller"), ) if err = memcachedReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Memcached") os.Exit(1) } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } } ``` -------------------------------- ### Installing Custom Resources for memcached-operator (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This command applies the custom resource definitions (CRDs) and sample instances from the `config/samples/` directory to the Kubernetes cluster. This is a prerequisite for the operator to manage its custom resources. ```Shell kubectl apply -f config/samples/ ``` -------------------------------- ### Installing memcached-operator CRDs for Testing (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This `make` command installs the Custom Resource Definitions (CRDs) for the memcached-operator into the Kubernetes cluster, typically used as a prerequisite for local testing or development. ```Shell make install ``` -------------------------------- ### Installing CRDs and Running Controller Locally in One Step Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This combined `make` command first installs the necessary Custom Resource Definitions (CRDs) and then immediately runs the Elasticsearch controller locally. It's a convenient shortcut for setting up a local testing environment. ```sh make install run ``` -------------------------------- ### Installing CRDs for Local Controller Testing Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This `make` command installs the Custom Resource Definitions (CRDs) into the Kubernetes cluster, which is a necessary step before running the controller locally for testing purposes. ```sh make install ``` -------------------------------- ### Installing Custom Resources using kubectl Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This command applies the Custom Resource Definitions (CRDs) and sample instances defined in the `config/samples/` directory to the Kubernetes cluster. This is a prerequisite for the operator to manage Elasticsearch instances. ```sh kubectl apply -f config/samples/ ``` -------------------------------- ### Defining RBAC Permissions for Memcached Operator in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This section specifies the Kubernetes Role-Based Access Control (RBAC) rules required for the Memcached operator. It grants permissions to `get`, `list`, `watch`, `create`, `update`, `patch`, and `delete` Memcached custom resources, their statuses and finalizers, along with permissions for core Kubernetes resources like events, configmaps, deployments, and pods. ```Go //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update //+kubebuilder:rbac:groups="core",resources=events,verbs=patch;get;create //+kubebuilder:rbac:groups="core",resources=configmaps,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups="apps",resources=deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch ``` -------------------------------- ### Retrieving Memcached Operand Image in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This function retrieves the Memcached operand image. It first attempts to get the image from the `MEMCACHED_IMAGE` environment variable, which is expected to be defined in `config/manager/manager.yaml`. If the environment variable is not found, it defaults to `memcached:1.4.36-alpine`. ```Go // imageForMemcached gets the Operand image which is managed by this controller // from the MEMCACHED_IMAGE environment variable defined in the config/manager/manager.yaml func imageForMemcached() (string, error) { var imageEnvVar = "MEMCACHED_IMAGE" image, found := os.LookupEnv(imageEnvVar) if !found { return "memcached:1.4.36-alpine", nil } return image, nil } ``` -------------------------------- ### Defining Elasticsearch Role Reconciler in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/remote-reconciler.md This Go code defines the `roleReconciler` struct, which implements `controller.RemoteReconcilerAction` for managing Elasticsearch roles. It includes the `newRoleReconciler` function for instantiation and the `GetRemoteHandler` method, which is responsible for obtaining an `eshandler.ElasticsearchHandler` client to interact with the Elasticsearch API based on the `Role` object's specifications. This setup allows the operator to reconcile Kubernetes `Role` resources with actual Elasticsearch security roles. ```golang package controllers import ( "context" "time" eshandler "github.com/disaster37/es-handler/v8" "github.com/disaster37/operator-sdk-extra/v2/pkg/controller" "github.com/disaster37/operator-sdk-extra/v2/pkg/object" elasticsearchapicrd "github.com/disaster37/operator-sdk-extra/v2/testdata/elasticsearch-operator/api/v1alpha1" "github.com/sirupsen/logrus" k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) type roleReconciler struct { controller.RemoteReconcilerAction[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler] controller.BaseReconciler } func newRoleReconciler(client client.Client, logger *logrus.Entry, recorder record.EventRecorder) controller.RemoteReconcilerAction[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler] { return &roleReconciler{ RemoteReconcilerAction: controller.NewRemoteReconcilerAction[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler]( client, logger, recorder, ), BaseReconciler: controller.BaseReconciler{ Client: client, Log: logger, Recorder: recorder, }, } } func (h *roleReconciler) GetRemoteHandler(ctx context.Context, req ctrl.Request, o object.RemoteObject) (handler controller.RemoteExternalReconciler[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler], res ctrl.Result, err error) { role := o.(*elasticsearchapicrd.Role) esClient, err := GetElasticsearchHandler(ctx, role, role.Spec.ElasticsearchRef, h.BaseReconciler.Client, h.BaseReconciler.Log) if err != nil && role.DeletionTimestamp.IsZero() { return nil, res, err } // Elastic not ready if esClient == nil { return nil, ctrl.Result{RequeueAfter: 60 * time.Second}, nil } handler = newRoleApiClient(esClient) return handler, res, nil } ``` -------------------------------- ### Implementing Remote External Reconciler for Elasticsearch Roles in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/remote-reconciler.md This Go code defines `roleApiClient`, a remote external reconciler for managing Elasticsearch roles. It implements methods like `Build` to convert Kubernetes `Role` CRD objects into `eshandler.XPackSecurityRole` objects, and `Get`, `Create`, `Update`, `Delete` to perform corresponding operations on the Elasticsearch API. The `Build` method specifically handles unmarshalling JSON strings from the CRD spec into Go maps for fields like `Global`, `Metadata`, `TransientMetadata`, `Applications`, and `Indices`. ```Go package controllers import ( "encoding/json" eshandler "github.com/disaster37/es-handler/v8" "github.com/disaster37/generic-objectmatcher/patch" "github.com/disaster37/operator-sdk-extra/v2/pkg/controller" elasticsearchapicrd "github.com/disaster37/operator-sdk-extra/v2/testdata/elasticsearch-operator/api/v1alpha1" ) type roleApiClient struct { *controller.BasicRemoteExternalReconciler[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler] } func newRoleApiClient(client eshandler.ElasticsearchHandler) controller.RemoteExternalReconciler[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler] { return &roleApiClient{ BasicRemoteExternalReconciler: controller.NewBasicRemoteExternalReconciler[*elasticsearchapicrd.Role, *eshandler.XPackSecurityRole, eshandler.ElasticsearchHandler](client), } } func (h *roleApiClient) Build(o *elasticsearchapicrd.Role) (role *eshandler.XPackSecurityRole, err error) { role = &eshandler.XPackSecurityRole{ Cluster: o.Spec.Cluster, RunAs: o.Spec.RunAs, } if o.Spec.Global != "" { global := make(map[string]any) if err := json.Unmarshal([]byte(o.Spec.Global), &global); err != nil { return nil, err } role.Global = global } if o.Spec.Metadata != "" { meta := make(map[string]any) if err := json.Unmarshal([]byte(o.Spec.Metadata), &meta); err != nil { return nil, err } role.Metadata = meta } if o.Spec.TransientMetadata != "" { tm := make(map[string]any) if err := json.Unmarshal([]byte(o.Spec.TransientMetadata), &tm); err != nil { return nil, err } role.TransientMetadata = tm } if o.Spec.Applications != nil { role.Applications = make([]eshandler.XPackSecurityApplicationPrivileges, 0, len(o.Spec.Applications)) for _, application := range o.Spec.Applications { role.Applications = append(role.Applications, eshandler.XPackSecurityApplicationPrivileges{ Application: application.Application, Privileges: application.Privileges, Resources: application.Resources, }) } } if o.Spec.Indices != nil { role.Indices = make([]eshandler.XPackSecurityIndicesPermissions, 0, len(o.Spec.Indices)) for _, indice := range o.Spec.Indices { i := eshandler.XPackSecurityIndicesPermissions{ Names: indice.Names, Privileges: indice.Privileges, Query: indice.Query, AllowRestrictedIndices: indice.AllowRestrictedIndices, } if indice.FieldSecurity != "" { fs := make(map[string]any) if err := json.Unmarshal([]byte(indice.FieldSecurity), &fs); err != nil { return nil, err } i.FieldSecurity = fs } role.Indices = append(role.Indices, i) } } return role, nil } func (h *roleApiClient) Get(o *elasticsearchapicrd.Role) (object *eshandler.XPackSecurityRole, err error) { return h.Client().RoleGet(o.GetExternalName()) } func (h *roleApiClient) Create(object *eshandler.XPackSecurityRole, o *elasticsearchapicrd.Role) (err error) { return h.Client().RoleUpdate(o.GetExternalName(), object) } func (h *roleApiClient) Update(object *eshandler.XPackSecurityRole, o *elasticsearchapicrd.Role) (err error) { return h.Client().RoleUpdate(o.GetExternalName(), object) } func (h *roleApiClient) Delete(o *elasticsearchapicrd.Role) (err error) { return h.Client().RoleDelete(o.GetExternalName()) } ``` -------------------------------- ### Bootstrapping New Operator with Operator SDK Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This snippet demonstrates the initial steps to set up a new Kubernetes operator project using the `operator-sdk`. It initializes the project with a specified domain and repository, then creates a new API group, version, and kind for a custom resource (Memcached) with resource and controller scaffolding. ```bash operator-sdk init --domain=example.com --repo=github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller ``` -------------------------------- ### Formatting Go Code with Dagger (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This command leverages Dagger's Go module to automatically format the Go source code within the current project directory, ensuring consistent code style across the codebase. ```bash dagger call -m golang --src . format export --path . ``` -------------------------------- ### Running All CI Steps Locally (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This Dagger command executes all defined continuous integration steps locally for the project, excluding the final image push, allowing developers to validate the pipeline before committing changes. ```bash dagger call --src . ci ``` -------------------------------- ### Linting Go Project with Dagger (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This Dagger command runs linting checks on the Go project using the integrated Go module, identifying potential errors, style violations, and suspicious constructs in the code. ```bash dagger call -m golang --src . lint ``` -------------------------------- ### Running Local Tests with Envtest and Gotestsum (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This Dagger command executes local tests for the project, specifically using `envtest` for Kubernetes environment simulation and integrating `gotestsum` for enhanced test reporting. ```bash dagger call --src . test --withGotestsum ``` -------------------------------- ### Building and Pushing memcached-operator Docker Image (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This `make` command builds the Docker image for the memcached-operator and pushes it to the specified image registry. The `IMG` variable must be set to the target registry and image tag. ```Shell make docker-build docker-push IMG=/memcached-operator:tag ``` -------------------------------- ### Initializing Operator SDK Manager and Role Reconciler in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/remote-reconciler.md This Go snippet demonstrates the main entry point for an Operator SDK-based controller. It initializes the controller manager with various configurations like metrics, webhooks, and leader election. It then sets up a custom `RoleReconciler` and registers it with the manager, enabling it to watch and reconcile Kubernetes resources. The snippet also includes health and readiness checks. ```Go /* Copyright 2023. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "flag" "os" // 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" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" elasticsearchapiv1alpha1 "github.com/disaster37/operator-sdk-extra/v2/testdata/elasticsearch-operator/api/v1alpha1" "github.com/disaster37/operator-sdk-extra/v2/testdata/elasticsearch-operator/controllers" "github.com/sirupsen/logrus" //+kubebuilder:scaffold:imports ) var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(elasticsearchapiv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: server.Options{ BindAddress: metricsAddr, }, WebhookServer: webhook.NewServer(webhook.Options{ Port: 9443, }), HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "7d8bc9de.example.com", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly // speeds up voluntary leader transitions as the new leader don't have to wait // LeaseDuration time first. // // In the default scaffold provided, the program ends immediately after // the manager stops, so would be fine to enable this option. However, // if you are doing or is intended to do any operation such as perform cleanups // after the manager stops then its usage might be unsafe. // LeaderElectionReleaseOnCancel: true, }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } log := logrus.New() log.SetLevel(logrus.DebugLevel) memcachedReconciler := controllers.NewRoleReconciler( mgr.GetClient(), logrus.NewEntry(log), mgr.GetEventRecorderFor("role-controller"), ) if err = memcachedReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Role") os.Exit(1) } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } } ``` -------------------------------- ### Setting Up Memcached Controller with Manager in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This snippet defines the `SetupWithManager` method, which integrates the `MemcachedReconciler` with the Kubernetes controller-runtime manager. It configures the controller to watch for `Memcached` custom resources and to own `Deployment` and `ConfigMap` resources, ensuring that changes to these owned resources trigger reconciliation of the parent `Memcached` object. ```Go // SetupWithManager sets up the controller with the Manager. func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&cachecrd.Memcached{}). Owns(&appv1.Deployment{}). Owns(&corev1.ConfigMap{}). Complete(r) } ``` -------------------------------- ### Performing Go Vulnerability Check with Dagger (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This command utilizes Dagger's Go module to perform a vulnerability scan on the Go project's dependencies, helping to identify and mitigate known security vulnerabilities. ```bash dagger call -m golang --src . vulncheck ``` -------------------------------- ### Defining and Initializing MemcachedReconciler in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This snippet defines the `MemcachedReconciler` struct, embedding necessary interfaces from `operator-sdk-extra` for multi-phase reconciliation. It also provides the `NewMemcachedReconciler` constructor, which initializes the controller with basic components like client, logger, recorder, and specific multi-phase reconciler actions and settings, including a finalizer. ```Go /* Copyright 2023. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package controllers import ( "context" appv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/disaster37/operator-sdk-extra/v2/pkg/controller" cachecrd "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" "github.com/sirupsen/logrus" ) // MemcachedReconciler reconciles a Memcached object type MemcachedReconciler struct { controller.Controller controller.MultiPhaseReconcilerAction controller.MultiPhaseReconciler controller.BaseReconciler } func NewMemcachedReconciler(client client.Client, logger *logrus.Entry, recorder record.EventRecorder) (multiPhaseReconciler controller.Controller) { return &MemcachedReconciler{ Controller: controller.NewBasicController(), MultiPhaseReconcilerAction: controller.NewBasicMultiPhaseReconcilerAction( client, controller.ReadyCondition, logger, recorder, ), MultiPhaseReconciler: controller.NewBasicMultiPhaseReconciler( client, "memcached", "memcached.cache.example.com/finalizer", logger, recorder, ), BaseReconciler: controller.BaseReconciler{ Client: client, Recorder: recorder, Log: logger, }, } } ``` -------------------------------- ### Generating Operator SDK Manifests (Bash) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/CONTRIBUTE.md This command uses Dagger's Operator SDK module to generate Kubernetes manifests for the operator, exporting them to the current directory, which is crucial for deployment and packaging. ```bash dagger call -m operator-sdk --src . sdk generate-manifests export --path . ``` -------------------------------- ### Deploying memcached-operator Controller to Cluster (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This `make` command deploys the memcached-operator controller to the Kubernetes cluster. It uses the Docker image specified by the `IMG` variable, which should have been previously built and pushed. ```Shell make deploy IMG=/memcached-operator:tag ``` -------------------------------- ### Running memcached-operator Controller Locally (Shell) Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/memcached-operator/README.md This `make` command runs the memcached-operator controller locally in the foreground. It connects to the configured Kubernetes cluster and begins reconciling resources. This is useful for local development and debugging. ```Shell make run ``` -------------------------------- ### Building Memcached Deployment in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This Go function `newDeploymentsBuilder` creates a Kubernetes `Deployment` object for a Memcached instance. It takes a `v1alpha1.Memcached` custom resource, extracts configuration like replicas and image, and constructs a `DeploymentSpec` including pod and container security contexts, ports, commands, and environment variables sourced from a ConfigMap. It also includes commented-out sections for node affinity configuration. ```Go package controllers import ( "os" "strings" "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" "github.com/thoas/go-funk" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func newDeploymentsBuilder(memcached *v1alpha1.Memcached) (deployments []appsv1.Deployment, err error) { deployments = make([]appsv1.Deployment, 0, 1) ls := labelsForMemcached(memcached.Name) replicas := memcached.Spec.Size // Get the Operand image image, err := imageForMemcached() if err != nil { return nil, err } dep := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: memcached.Name, Namespace: memcached.Namespace, Labels: funk.UnionStringMap( ls, memcached.Labels, ), Annotations: memcached.GetAnnotations(), }, Spec: appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: ls, }, Spec: corev1.PodSpec{ // TODO(user): Uncomment the following code to configure the nodeAffinity expression // according to the platforms which are supported by your solution. It is considered // best practice to support multiple architectures. build your manager image using the // makefile target docker-buildx. Also, you can use docker manifest inspect // to check what are the platforms supported. // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity //Affinity: &corev1.Affinity{ // NodeAffinity: &corev1.NodeAffinity{ // RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ // NodeSelectorTerms: []corev1.NodeSelectorTerm{ // { // MatchExpressions: []corev1.NodeSelectorRequirement{ // { // Key: "kubernetes.io/arch", // Operator: "In", // Values: []string{"amd64", "arm64", "ppc64le", "s390x"}, // }, // { // Key: "kubernetes.io/os", // Operator: "In", // Values: []string{"linux"}, // }, // }, // }, // }, // }, //}, SecurityContext: &corev1.PodSecurityContext{ RunAsNonRoot: &[]bool{true}[0], // IMPORTANT: seccomProfile was introduced with Kubernetes 1.19 // If you are looking for to produce solutions to be supported // on lower versions you must remove this option. SeccompProfile: &corev1.SeccompProfile{ Type: corev1.SeccompProfileTypeRuntimeDefault, }, }, Containers: []corev1.Container{{ Image: image, Name: "memcached", ImagePullPolicy: corev1.PullIfNotPresent, // Ensure restrictive context for the container // More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted SecurityContext: &corev1.SecurityContext{ // WARNING: Ensure that the image used defines an UserID in the Dockerfile // otherwise the Pod will not run and will fail with "container has runAsNonRoot and image has non-numeric user"". // If you want your workloads admitted in namespaces enforced with the restricted mode in OpenShift/OKD vendors // then, you MUST ensure that the Dockerfile defines a User ID OR you MUST leave the "RunAsNonRoot" and // "RunAsUser" fields empty. RunAsNonRoot: &[]bool{true}[0], // The memcached image does not use a non-zero numeric user as the default user. // Due to RunAsNonRoot field being set to true, we need to force the user in the // container to a non-zero numeric user. We do this using the RunAsUser field. // However, if you are looking to provide solution for K8s vendors like OpenShift // be aware that you cannot run under its restricted-v2 SCC if you set this value. RunAsUser: &[]int64{1001}[0], AllowPrivilegeEscalation: &[]bool{false}[0], Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{ "ALL", }, }, }, Ports: []corev1.ContainerPort{{ ContainerPort: memcached.Spec.ContainerPort, Name: "memcached", }}, Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, EnvFrom: []corev1.EnvFromSource{ { ConfigMapRef: &corev1.ConfigMapEnvSource{ LocalObjectReference: corev1.LocalObjectReference{ Name: memcached.Name, }, }, }, }, }}, }, }, }, } deployments = append(deployments, *dep) return deployments, nil } // labelsForMemcached returns the labels for selecting the resources ``` -------------------------------- ### Initializing and Creating API for Operator SDK Project Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/remote-reconciler.md These commands bootstrap a new operator project and then create a new API definition for a custom resource (CRD) named 'Role' within the 'elasticsearchapi' group and 'v1alpha1' version. The `--resource` and `--controller` flags indicate that both the CRD definition and its corresponding controller should be generated. ```bash operator-sdk init --domain=example.com --repo=github.com/disaster37/operator-sdk-extra/v2/testdata/elasticsearch-operator operator-sdk create api --group elasticsearchapi --version v1alpha1 --kind Role --resource --controller ``` -------------------------------- ### Implementing Deployment Reconciler Read Method in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This Go code defines a `deploymentReconciler` that implements the `Read` method of the `controller.MultiPhaseStepReconcilerAction` interface. The `Read` method is responsible for fetching existing Kubernetes Deployments based on specific labels and then generating the expected Deployments. It uses `operator-sdk-extra` for multi-phase reconciliation and error handling. ```Go package controllers import ( "context" "fmt" "emperror.dev/errors" "github.com/disaster37/operator-sdk-extra/v2/pkg/apis/shared" "github.com/disaster37/operator-sdk-extra/v2/pkg/controller" "github.com/disaster37/operator-sdk-extra/v2/pkg/helper" "github.com/disaster37/operator-sdk-extra/v2/pkg/object" "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" "github.com/sirupsen/logrus" appv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( DeploymentCondition shared.ConditionName = "DeploymentReady" DeploymentPhase shared.PhaseName = "Deployment" ) type deploymentReconciler struct { controller.MultiPhaseStepReconcilerAction controller.BaseReconciler } func newDeploymentReconciler(client client.Client, logger *logrus.Entry, recorder record.EventRecorder) (multiPhaseStepReconcilerAction *deploymentReconciler) { return &deploymentReconciler{ MultiPhaseStepReconcilerAction: controller.NewBasicMultiPhaseStepReconcilerAction( client, DeploymentPhase, DeploymentCondition, logger, recorder, ), BaseReconciler: controller.BaseReconciler{ Client: client, Recorder: recorder, Log: logger, }, } } func (r *deploymentReconciler) Read(ctx context.Context, o object.MultiPhaseObject, data map[string]any) (read controller.MultiPhaseRead, res ctrl.Result, err error) { mc := o.(*v1alpha1.Memcached) deploymentList := &appv1.DeploymentList{} read = controller.NewBasicMultiPhaseRead() // Read current configmaps labelSelectors, err := labels.Parse(fmt.Sprintf("name=%s,%s=true", o.GetName(), v1alpha1.MemcachedAnnotationKey)) if err != nil { return read, res, errors.Wrap(err, "Error when generate label selector") } if err = r.Client.List(ctx, deploymentList, &client.ListOptions{Namespace: o.GetNamespace(), LabelSelector: labelSelectors}); err != nil { return read, res, errors.Wrapf(err, "Error when read deployments") } read.SetCurrentObjects(helper.ToSliceOfObject(deploymentList.Items)) // Generate expected configmaps expectedDeployments, err := newDeploymentsBuilder(mc) if err != nil { return read, res, errors.Wrap(err, "Error when generate expected deployments") } read.SetExpectedObjects(helper.ToSliceOfObject(expectedDeployments)) return read, res, nil } ``` -------------------------------- ### Building and Pushing Docker Image for Elasticsearch Operator Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This `make` command builds the Docker image for the Elasticsearch operator and pushes it to the specified container registry. The `IMG` variable must be set to the target registry and image tag. ```sh make docker-build docker-push IMG=/elasticsearch-operator:tag ``` -------------------------------- ### Implementing ConfigMap Read Method for Multi-Phase Reconciliation in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This Go struct `configMapReconciler` implements the `MultiPhaseStepReconcilerAction` interface, specifically its `Read` method. The `Read` method fetches existing ConfigMaps from Kubernetes based on labels, then generates the expected ConfigMaps using `newConfigMapsBuilder`, and finally sets both current and expected objects for reconciliation. It uses `operator-sdk-extra` for multi-phase reconciliation logic. ```Go package controllers import ( "context" "fmt" "emperror.dev/errors" "github.com/disaster37/operator-sdk-extra/v2/pkg/apis/shared" "github.com/disaster37/operator-sdk-extra/v2/pkg/controller" "github.com/disaster37/operator-sdk-extra/v2/pkg/helper" "github.com/disaster37/operator-sdk-extra/v2/pkg/object" "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( ConfigmapCondition shared.ConditionName = "ConfigmapReady" ConfigmapPhase shared.PhaseName = "Configmap" ) type configMapReconciler struct { controller.MultiPhaseStepReconcilerAction controller.BaseReconciler } func newConfigMapReconciler(client client.Client, logger *logrus.Entry, recorder record.EventRecorder) (multiPhaseStepReconcilerAction controller.MultiPhaseStepReconcilerAction) { return &configMapReconciler{ MultiPhaseStepReconcilerAction: controller.NewBasicMultiPhaseStepReconcilerAction( client, ConfigmapPhase, ConfigmapCondition, logger, recorder, ), BaseReconciler: controller.BaseReconciler{ Client: client, Recorder: recorder, Log: logger, }, } } func (r *configMapReconciler) Read(ctx context.Context, o object.MultiPhaseObject, data map[string]any) (read controller.MultiPhaseRead, res ctrl.Result, err error) { mc := o.(*v1alpha1.Memcached) cmList := &corev1.ConfigMapList{} read = controller.NewBasicMultiPhaseRead() // Read current configmaps labelSelectors, err := labels.Parse(fmt.Sprintf("name=%s,%s=true", o.GetName(), v1alpha1.MemcachedAnnotationKey)) if err != nil { return read, res, errors.Wrap(err, "Error when generate label selector") } if err = r.Client.List(ctx, cmList, &client.ListOptions{Namespace: o.GetNamespace(), LabelSelector: labelSelectors}); err != nil { return read, res, errors.Wrapf(err, "Error when read configmaps") } read.SetCurrentObjects(helper.ToSliceOfObject(cmList.Items)) // Generate expected configmaps expectedCms, err := newConfigMapsBuilder(mc) if err != nil { return read, res, errors.Wrap(err, "Error when generate expected configMaps") } read.SetExpectedObjects(helper.ToSliceOfObject(expectedCms)) return read, res, nil } ``` -------------------------------- ### Building Expected ConfigMaps for Memcached Operator in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This Go function `newConfigMapsBuilder` generates a slice of `corev1.ConfigMap` objects based on the provided `Memcached` custom resource. It sets the ConfigMap's name, namespace, and labels, including a specific annotation key, and populates its `Data` field with the instance name. This function is used to define the desired state of ConfigMaps. ```Go package controllers import ( "github.com/disaster37/operator-sdk-extra/v2/testdata/memcached-operator/api/v1alpha1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func newConfigMapsBuilder(o *v1alpha1.Memcached) (configMaps []corev1.ConfigMap, err error) { configMaps = make([]corev1.ConfigMap, 0, 1) cm := &corev1.ConfigMap{ ObjectMeta: v1.ObjectMeta{ Name: o.Name, Namespace: o.Namespace, Labels: map[string]string{ "name": o.GetName(), v1alpha1.MemcachedAnnotationKey: "true", }, }, Data: map[string]string{ "INSTANCE_NAME": o.Name, }, } configMaps = append(configMaps, *cm) return configMaps, nil } ``` -------------------------------- ### Generating Kubernetes Labels for Memcached in Go Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/documentations/multi-phase-reconciler.md This function generates a map of standard Kubernetes labels for a Memcached resource. It includes common labels like application name, instance, version (derived from the image tag), part-of, and created-by, along with a custom annotation for the Memcached operator. It depends on the `imageForMemcached` function to determine the image version. ```Go // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ func labelsForMemcached(name string) map[string]string { var imageTag string image, err := imageForMemcached() if err == nil { imageTag = strings.Split(image, ":")[1] } return map[string]string{"app.kubernetes.io/name": "Memcached", "app.kubernetes.io/instance": name, "app.kubernetes.io/version": imageTag, "app.kubernetes.io/part-of": "memcached-operator", "app.kubernetes.io/created-by": "controller-manager", "name": name, v1alpha1.MemcachedAnnotationKey: "true", } } ``` -------------------------------- ### Running Elasticsearch Controller Locally Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This `make` command runs the Elasticsearch controller locally in the foreground. This is typically used for development and testing, allowing direct interaction and debugging of the controller logic. ```sh make run ``` -------------------------------- ### Deploying Elasticsearch Operator to Kubernetes Cluster Source: https://github.com/disaster37/operator-sdk-extra/blob/v2/samples/elasticsearch-operator/README.md This `make` command deploys the Elasticsearch operator to the Kubernetes cluster. It uses the Docker image specified by the `IMG` variable, which should have been previously built and pushed. ```sh make deploy IMG=/elasticsearch-operator:tag ```