From f14dbc11209bdef308648b0f0f07fd2fe7e63059 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 31 Jan 2024 14:34:15 +0100 Subject: [PATCH] Clean up getting started docs Add generation of getting started project code This adds a step to generate the code used in the Getting Started docs page. This allows us to reference the pre-generated code relative to the docs page so we can keep things in sync between the generated code and the examples given in the docs. Fix various typos and grammar in v1alpha1 template Addresses various spelling and grammar mistakes found in the template used to generate the v1alpha1 sample projects. Addresses various typos, grammatical errors, and other refinement in the Getting Started docs page. Where possible, source files from the generated testdata project are imported directly in the docs so they stay up to date. In other areas where it wasn't practical to pull in the entire source file, this updates the code snippet shown to match the generated source and provides a link to the GitHub repo to use as a reference. Signed-off-by: Sean McGinnis --- docs/book/src/getting-started.md | 112 ++--- .../testdata/project/.dockerignore | 3 + .../testdata/project/.gitignore | 28 ++ .../testdata/project/.golangci.yml | 40 ++ .../testdata/project/Dockerfile | 33 ++ .../getting-started/testdata/project/Makefile | 204 ++++++++ .../getting-started/testdata/project/PROJECT | 33 ++ .../testdata/project/README.md | 114 +++++ .../project/api/v1alpha1/groupversion_info.go | 36 ++ .../project/api/v1alpha1/memcached_types.go | 85 ++++ .../api/v1alpha1/zz_generated.deepcopy.go | 123 +++++ .../testdata/project/cmd/main.go | 106 +++++ .../bases/cache.example.com_memcacheds.yaml | 298 ++++++++++++ .../project/config/crd/kustomization.yaml | 23 + .../project/config/crd/kustomizeconfig.yaml | 19 + .../project/config/default/kustomization.yaml | 146 ++++++ .../default/manager_auth_proxy_patch.yaml | 34 ++ .../config/default/manager_config_patch.yaml | 20 + .../manager/controller_manager_config.yaml | 20 + .../project/config/manager/kustomization.yaml | 10 + .../project/config/manager/manager.yaml | 103 ++++ .../config/prometheus/kustomization.yaml | 2 + .../project/config/prometheus/monitor.yaml | 25 + .../rbac/auth_proxy_client_clusterrole.yaml | 16 + .../project/config/rbac/auth_proxy_role.yaml | 24 + .../config/rbac/auth_proxy_role_binding.yaml | 19 + .../config/rbac/auth_proxy_service.yaml | 21 + .../project/config/rbac/kustomization.yaml | 18 + .../config/rbac/leader_election_role.yaml | 44 ++ .../rbac/leader_election_role_binding.yaml | 19 + .../config/rbac/memcached_editor_role.yaml | 31 ++ .../config/rbac/memcached_viewer_role.yaml | 27 ++ .../testdata/project/config/rbac/role.yaml | 59 +++ .../project/config/rbac/role_binding.yaml | 19 + .../project/config/rbac/service_account.yaml | 12 + .../samples/cache_v1alpha1_memcached.yaml | 11 + .../project/config/samples/kustomization.yaml | 4 + .../getting-started/testdata/project/go.mod | 73 +++ .../getting-started/testdata/project/go.sum | 205 ++++++++ .../testdata/project/hack/boilerplate.go.txt | 15 + .../controller/memcached_controller.go | 439 ++++++++++++++++++ .../controller/memcached_controller_test.go | 154 ++++++ .../project/internal/controller/suite_test.go | 90 ++++ .../project/test/e2e/e2e_suite_test.go | 32 ++ .../testdata/project/test/e2e/e2e_test.go | 121 +++++ .../testdata/project/test/utils/utils.go | 140 ++++++ hack/docs/generate_samples.go | 61 ++- .../cronjob-tutorial/generate_cronjob.go | 5 + .../generate_getting_started.go | 177 +++++++ .../templates/controllers/controller.go | 58 +-- .../internal/controller/busybox_controller.go | 32 +- .../controller/memcached_controller.go | 32 +- 52 files changed, 3430 insertions(+), 145 deletions(-) create mode 100644 docs/book/src/getting-started/testdata/project/.dockerignore create mode 100644 docs/book/src/getting-started/testdata/project/.gitignore create mode 100644 docs/book/src/getting-started/testdata/project/.golangci.yml create mode 100644 docs/book/src/getting-started/testdata/project/Dockerfile create mode 100644 docs/book/src/getting-started/testdata/project/Makefile create mode 100644 docs/book/src/getting-started/testdata/project/PROJECT create mode 100644 docs/book/src/getting-started/testdata/project/README.md create mode 100644 docs/book/src/getting-started/testdata/project/api/v1alpha1/groupversion_info.go create mode 100644 docs/book/src/getting-started/testdata/project/api/v1alpha1/memcached_types.go create mode 100644 docs/book/src/getting-started/testdata/project/api/v1alpha1/zz_generated.deepcopy.go create mode 100644 docs/book/src/getting-started/testdata/project/cmd/main.go create mode 100644 docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/crd/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/crd/kustomizeconfig.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/default/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/default/manager_auth_proxy_patch.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/default/manager_config_patch.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/manager/controller_manager_config.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/manager/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/manager/manager.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/prometheus/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/prometheus/monitor.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_client_clusterrole.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role_binding.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_service.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role_binding.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/memcached_editor_role.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/memcached_viewer_role.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/role.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/role_binding.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/rbac/service_account.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml create mode 100644 docs/book/src/getting-started/testdata/project/config/samples/kustomization.yaml create mode 100644 docs/book/src/getting-started/testdata/project/go.mod create mode 100644 docs/book/src/getting-started/testdata/project/go.sum create mode 100644 docs/book/src/getting-started/testdata/project/hack/boilerplate.go.txt create mode 100644 docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go create mode 100644 docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go create mode 100644 docs/book/src/getting-started/testdata/project/internal/controller/suite_test.go create mode 100644 docs/book/src/getting-started/testdata/project/test/e2e/e2e_suite_test.go create mode 100644 docs/book/src/getting-started/testdata/project/test/e2e/e2e_test.go create mode 100644 docs/book/src/getting-started/testdata/project/test/utils/utils.go create mode 100644 hack/docs/internal/getting-started/generate_getting_started.go diff --git a/docs/book/src/getting-started.md b/docs/book/src/getting-started.md index 6af3298e229..22bfcec5cbc 100644 --- a/docs/book/src/getting-started.md +++ b/docs/book/src/getting-started.md @@ -16,7 +16,7 @@ We will create a sample project to let you know how it works. This sample will: - Not allow more instances than the size defined in the CR which will be applied - Update the Memcached CR status -Following the steps. +Use the following steps. ## Create a project @@ -33,7 +33,7 @@ kubebuilder init --domain=example.com Next, we'll create a new API responsible for deploying and managing our Memcached solution. In this instance, we will utilize the [Deploy Image Plugin][deploy-image] to get a comprehensive code implementation for our solution. ``` -kubebuilder create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.4.36-alpine --image-container-command="memcached,-m=64,-o,modern,-v" --image-container-port="11211" --run-as-user="1001" --plugins="deploy-image/v1-alpha" --make=false +kubebuilder create api --group cache --version v1alpha1 --kind Memcached --image=memcached:1.4.36-alpine --image-container-command="memcached,-m=64,-o,modern,-v" --image-container-port="11211" --run-as-user="1001" --plugins="deploy-image/v1-alpha" --make=false ``` ### Understanding APIs @@ -45,7 +45,7 @@ This command's primary aim is to produce the Custom Resource (CR) and Custom Res Consider a typical scenario where the objective is to run an application and its database on a Kubernetes platform. In this context, one object might represent the Frontend App, while another denotes the backend Data Base. If we define one CRD for the App and another for the DB, we uphold essential concepts like encapsulation, the single responsibility principle, and cohesion. Breaching these principles might lead to complications, making extension, reuse, or maintenance challenging. -In essence, the App CRD and the DB CRD will have their controller. Let's say, for instance, that the application requires a Deployment and Service to run. In this example, the App’s Controller will cater to these needs. Similarly, the DB’s controller will manage the business logic of its items. +In essence, the App CRD and the DB CRD will each have their own controller. Let's say, for instance, that the application requires a Deployment and Service to run. In this example, the App’s Controller will cater to these needs. Similarly, the DB’s controller will manage the business logic of its items. Therefore, for each CRD, there should be one distinct controller, adhering to the design outlined by the [controller-runtime][controller-runtime]. For further information see [Groups and Versions and Kinds, oh my!][group-kind-oh-my]. @@ -94,32 +94,32 @@ type MemcachedStatus struct { } ``` -Thus, when we introduce new specifications to this file and execute the `make generate` command, we utilize [controller-gen][controller-gen] to generate the CRD manifest, which is located under the `config/crds` directory. +Thus, when we introduce new specifications to this file and execute the `make generate` command, we utilize [controller-gen][controller-gen] to generate the CRD manifest, which is located under the `config/crd/bases` directory. #### Markers and validations -Moreover, it's important to note that we're employing `markers`, such as `+kubebuilder:validation:Minimum=1`. These markers help in defining validations and criteria, ensuring that data provided by users—when they create or edit a Custom Resource for the Memcached Kind—is properly validated. For a comprehensive list and details of available markers, refer [here][markers]. +Moreover, it's important to note that we're employing `markers`, such as `+kubebuilder:validation:Minimum=1`. These markers help in defining validations and criteria, ensuring that data provided by users — when they create or edit a Custom Resource for the Memcached Kind — is properly validated. For a comprehensive list and details of available markers, refer [the Markers documentation][markers]. Observe the validation schema within the CRD; this schema ensures that the Kubernetes API properly validates the Custom Resources (CRs) that are applied: -From: `config/crd/bases/example.com.testproject.org_memcacheds.yaml` -```yaml - description: MemcachedSpec defines the desired state of Memcached - properties: - containerPort: - description: Port defines the port that will be used to init the container - with the image - format: int32 - type: integer - size: - description: 'Size defines the number of Memcached instances The following - markers will use OpenAPI v3 schema to validate the value More info: - https://book.kubebuilder.io/reference/markers/crd-validation.html' - format: int32 - maximum: 3 ## See here from the marker +kubebuilder:validation:Maximum=3 - minimum: 1 ## See here from the marker +kubebuilder:validation:Minimum=1 - type: integer - type: object +From: [config/crd/bases/cache.example.com_memcacheds.yaml](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml) +```yaml +description: MemcachedSpec defines the desired state of Memcached +properties: + containerPort: + description: Port defines the port that will be used to init the container + with the image + format: int32 + type: integer + size: + description: 'Size defines the number of Memcached instances The following + markers will use OpenAPI v3 schema to validate the value More info: + https://book.kubebuilder.io/reference/markers/crd-validation.html' + format: int32 + maximum: 3 ## Generated from the marker +kubebuilder:validation:Maximum=3 + minimum: 1 ## Generated from the marker +kubebuilder:validation:Minimum=1 + type: integer +type: object ``` #### Sample of Custom Resources @@ -127,24 +127,15 @@ From: `config/crd/bases/example.com.testproject.org_memcacheds.yaml` The manifests located under the "config/samples" directory serve as examples of Custom Resources that can be applied to the cluster. In this particular example, by applying the given resource to the cluster, we would generate a Deployment with a single instance size (see `size: 1`). -From: `config/samples/example.com_v1alpha1_memcached.yaml` +From: [config/samples/cache_v1alpha1_memcached.yaml](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml) -```shell -apiVersion: example.com.testproject.org/v1alpha1 -kind: Memcached -metadata: - name: memcached-sample -spec: - # TODO(user): edit the following value to ensure the number - # of Pods/Instances your Operand must have on cluster - size: 1 -# TODO(user): edit the following value to ensure the container has the right port to be initialized - containerPort: 11211 +```yaml +{{#include ./getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml}} ``` ### Reconciliation Process -The reconciliation function plays a pivotal role in ensuring synchronization between resources and their specifications based on the business logic embedded within them. Essentially, it operates like a loop, continuously checking conditions and performing actions until all conditions align with its implementation. Here's a pseudo-code to illustrate this: +The reconciliation function plays a pivotal role in ensuring synchronization between resources and their specifications based on the business logic embedded within them. Essentially, it operates like a loop, continuously checking conditions and performing actions until all conditions align with its implementation. Here's pseudo-code to illustrate this: ```go reconcile App { @@ -206,9 +197,9 @@ return ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())}, nil #### In the context of our example -When a Custom Resource is applied to the cluster, there's a designated controller to manage the Memcached Kind. You can check its reconciliation implemented: +When a Custom Resource is applied to the cluster, there's a designated controller to manage the Memcached Kind. You can check how its reconciliation is implemented: -From `testdata/project-v4-with-deploy-image/internal/controller/memcached_controller.go`: +From: [internal/controller/memcached_controller.go](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go) ```go func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -221,7 +212,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( err := r.Get(ctx, req.NamespacedName, memcached) if err != nil { if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created + // If the custom resource is not found then it usually means that it was deleted or not created // In this way, we will stop the reconciliation log.Info("memcached resource not found. Ignoring since object must be deleted") return ctrl.Result{}, nil @@ -231,7 +222,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Let's just set the status as Unknown when no status are available + // Let's just set the status as Unknown when no status is available if memcached.Status.Conditions == nil || len(memcached.Status.Conditions) == 0 { meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) if err = r.Status().Update(ctx, memcached); err != nil { @@ -239,9 +230,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Let's re-fetch the memcached Custom Resource after update the status + // Let's re-fetch the memcached Custom Resource after updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation // if we try to update it again in the following operations if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { @@ -251,7 +242,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. + // occur before the custom resource to be deleted. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { log.Info("Adding Finalizer for Memcached") @@ -273,7 +264,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( if controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { log.Info("Performing Finalizer Operations for Memcached before delete CR") - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + // Let's add here a status "Downgrade" to reflect that this resource began its process to be terminated. meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, Status: metav1.ConditionUnknown, Reason: "Finalizing", Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", memcached.Name)}) @@ -283,7 +274,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Perform all operations required before remove the finalizer and allow + // Perform all operations required before removing the finalizer and allow // the Kubernetes API to remove the custom resource. r.doFinalizerOperationsForMemcached(memcached) @@ -291,9 +282,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // then you need to ensure that all worked fine before deleting and updating the Downgrade status // otherwise, you should requeue here. - // Re-fetch the memcached Custom Resource before update the status + // Re-fetch the memcached Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { log.Error(err, "Failed to re-fetch memcached") @@ -374,9 +365,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - // Re-fetch the memcached Custom Resource before update the status + // Re-fetch the memcached Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { log.Error(err, "Failed to re-fetch memcached") @@ -458,13 +449,12 @@ manifest files present in `config/rbac/`. These markers can be found (and should how it is implemented in our example: ```go -//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/finalizers,verbs=update +//+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=create;patch //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch - ``` It's important to highlight that if you wish to add or modify RBAC rules, you can do so by updating or adding the respective markers in the controller. @@ -473,9 +463,9 @@ After making the necessary changes, run the `make generate` command. This will p @@ -495,12 +485,12 @@ If you inspect the `cmd/main.go` file, you'll come across the following: // 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. + // speeds up voluntary leader transitions as the new leader doesn't have to wait + // the 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 + // the manager stops, so it would be fine to enable this option. However, + // if you are doing, or are intending to do, any operation such as perform cleanups // after the manager stops then its usage might be unsafe. // LeaderElectionReleaseOnCancel: true, }) @@ -516,7 +506,7 @@ that are produced for your operator's APIs. ### Checking the Project running in the cluster -At this point, you can primarily execute the commands highlighted in the [quick-start][quick-start]. +At this point, you can execute the commands highlighted in the [quick-start][quick-start]. By executing `make build IMG=myregistry/example:1.0.0`, you'll build the image for your project. For testing purposes, it's recommended to publish this image to a public registry. This ensures easy accessibility, eliminating the need for additional configurations. Once that's done, you can deploy the image to the cluster using the `make deploy IMG=myregistry/example:1.0.0` command. @@ -537,4 +527,4 @@ to the cluster using the `make deploy IMG=myregistry/example:1.0.0` command. [manager]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager [options-manager]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager#Options [quick-start]: ./quick-start.md -[best-practices]: ./reference/good-practices.md \ No newline at end of file +[best-practices]: ./reference/good-practices.md diff --git a/docs/book/src/getting-started/testdata/project/.dockerignore b/docs/book/src/getting-started/testdata/project/.dockerignore new file mode 100644 index 00000000000..a3aab7af70c --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/.dockerignore @@ -0,0 +1,3 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ diff --git a/docs/book/src/getting-started/testdata/project/.gitignore b/docs/book/src/getting-started/testdata/project/.gitignore new file mode 100644 index 00000000000..7a7feec5045 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/.gitignore @@ -0,0 +1,28 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/* +Dockerfile.cross + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Go workspace file +go.work + +# Kubernetes Generated files - skip generated files, except for vendored files +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +.vscode +*.swp +*.swo +*~ diff --git a/docs/book/src/getting-started/testdata/project/.golangci.yml b/docs/book/src/getting-started/testdata/project/.golangci.yml new file mode 100644 index 00000000000..aed8644d11e --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/.golangci.yml @@ -0,0 +1,40 @@ +run: + deadline: 5m + allow-parallel-runners: true + +issues: + # don't skip warning about doc comments + # don't exclude the default set of lint + exclude-use-default: false + # restore some of the defaults + # (fill in the rest as needed) + exclude-rules: + - path: "api/*" + linters: + - lll + - path: "internal/*" + linters: + - dupl + - lll +linters: + disable-all: true + enable: + - dupl + - errcheck + - exportloopref + - goconst + - gocyclo + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - prealloc + - staticcheck + - typecheck + - unconvert + - unparam + - unused diff --git a/docs/book/src/getting-started/testdata/project/Dockerfile b/docs/book/src/getting-started/testdata/project/Dockerfile new file mode 100644 index 00000000000..aca26f92295 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/Dockerfile @@ -0,0 +1,33 @@ +# Build the manager binary +FROM golang:1.21 AS builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY cmd/main.go cmd/main.go +COPY api/ api/ +COPY internal/controller/ internal/controller/ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/docs/book/src/getting-started/testdata/project/Makefile b/docs/book/src/getting-started/testdata/project/Makefile new file mode 100644 index 00000000000..c38d0294813 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/Makefile @@ -0,0 +1,204 @@ + +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.29.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# CONTAINER_TOOL defines the container tool to be used for building images. +# Be aware that the target commands are only tested with Docker which is +# scaffolded by default. However, you might want to replace it to use other +# tools. (i.e. podman) +CONTAINER_TOOL ?= docker + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk command is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out + +# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. +.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. +test-e2e: + go test ./test/e2e/ -v -ginkgo.v + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter & yamllint + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager cmd/main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./cmd/main.go + +# If you wish to build the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + $(CONTAINER_TOOL) push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ +# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) +# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name project-v3-builder + $(CONTAINER_TOOL) buildx use project-v3-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm project-v3-builder + rm Dockerfile.cross + +.PHONY: build-installer +build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. + mkdir -p dist + echo "---" > dist/install.yaml # Clean previous content + @if [ -d "config/crd" ]; then \ + $(KUSTOMIZE) build config/crd > dist/install.yaml; \ + echo "---" >> dist/install.yaml; \ + fi + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default >> dist/install.yaml + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - + +.PHONY: undeploy +undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUBECTL ?= kubectl +KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) +ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) + +## Tool Versions +KUSTOMIZE_VERSION ?= v5.3.0 +CONTROLLER_TOOLS_VERSION ?= v0.14.0 +ENVTEST_VERSION ?= latest +GOLANGCI_LINT_VERSION ?= v1.54.2 + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. +$(ENVTEST): $(LOCALBIN) + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) + +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary (ideally with version) +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f $(1) ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ +} +endef diff --git a/docs/book/src/getting-started/testdata/project/PROJECT b/docs/book/src/getting-started/testdata/project/PROJECT new file mode 100644 index 00000000000..9507e610e6e --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/PROJECT @@ -0,0 +1,33 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +componentConfig: true +domain: example.com +layout: +- go.kubebuilder.io/v4 +plugins: + deploy-image.go.kubebuilder.io/v1-alpha: + resources: + - domain: example.com + group: cache + kind: Memcached + options: + containerCommand: memcached,-m=64,-o,modern,-v + containerPort: "11211" + image: memcached:1.4.36-alpine + runAsUser: "1001" + version: v1alpha1 +projectName: project +repo: example.com/memcached +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: example.com + group: cache + kind: Memcached + path: example.com/memcached/api/v1alpha1 + version: v1alpha1 +version: "3" diff --git a/docs/book/src/getting-started/testdata/project/README.md b/docs/book/src/getting-started/testdata/project/README.md new file mode 100644 index 00000000000..3fca7276d17 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/README.md @@ -0,0 +1,114 @@ +# project +// TODO(user): Add simple overview of use/purpose + +## Description +// TODO(user): An in-depth paragraph about your project and overview of use + +## Getting Started + +### Prerequisites +- go version v1.21.0+ +- docker version 17.03+. +- kubectl version v1.11.3+. +- Access to a Kubernetes v1.11.3+ cluster. + +### To Deploy on the cluster +**Build and push your image to the location specified by `IMG`:** + +```sh +make docker-build docker-push IMG=/project:tag +``` + +**NOTE:** This image ought to be published in the personal registry you specified. +And it is required to have access to pull the image from the working environment. +Make sure you have the proper permission to the registry if the above commands don’t work. + +**Install the CRDs into the cluster:** + +```sh +make install +``` + +**Deploy the Manager to the cluster with the image specified by `IMG`:** + +```sh +make deploy IMG=/project:tag +``` + +> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin +privileges or be logged in as admin. + +**Create instances of your solution** +You can apply the samples (examples) from the config/sample: + +```sh +kubectl apply -k config/samples/ +``` + +>**NOTE**: Ensure that the samples has default values to test it out. + +### To Uninstall +**Delete the instances (CRs) from the cluster:** + +```sh +kubectl delete -k config/samples/ +``` + +**Delete the APIs(CRDs) from the cluster:** + +```sh +make uninstall +``` + +**UnDeploy the controller from the cluster:** + +```sh +make undeploy +``` + +## Project Distribution + +Following are the steps to build the installer and distribute this project to users. + +1. Build the installer for the image built and published in the registry: + +```sh +make build-installer IMG=/project:tag +``` + +NOTE: The makefile target mentioned above generates an 'install.yaml' +file in the dist directory. This file contains all the resources built +with Kustomize, which are necessary to install this project without +its dependencies. + +2. Using the installer + +Users can just run kubectl apply -f to install the project, i.e.: + +```sh +kubectl apply -f https://raw.githubusercontent.com//project//dist/install.yaml +``` + +## Contributing +// TODO(user): Add detailed information on how you would like others to contribute to this project + +**NOTE:** Run `make help` for more information on all potential `make` targets + +More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) + +## License + +Copyright 2024 The Kubernetes authors. + +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. + diff --git a/docs/book/src/getting-started/testdata/project/api/v1alpha1/groupversion_info.go b/docs/book/src/getting-started/testdata/project/api/v1alpha1/groupversion_info.go new file mode 100644 index 00000000000..6e3414e91b1 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/api/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 v1alpha1 contains API Schema definitions for the cache v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=cache.example.com +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "cache.example.com", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/docs/book/src/getting-started/testdata/project/api/v1alpha1/memcached_types.go b/docs/book/src/getting-started/testdata/project/api/v1alpha1/memcached_types.go new file mode 100644 index 00000000000..83b2e49eba7 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/api/v1alpha1/memcached_types.go @@ -0,0 +1,85 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + cfg "sigs.k8s.io/controller-runtime/pkg/config/v1alpha1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// MemcachedSpec defines the desired state of Memcached +type MemcachedSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Size defines the number of Memcached instances + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=3 + // +kubebuilder:validation:ExclusiveMaximum=false + Size int32 `json:"size,omitempty"` + + // Port defines the port that will be used to init the container with the image + ContainerPort int32 `json:"containerPort,omitempty"` +} + +// MemcachedStatus defines the observed state of Memcached +type MemcachedStatus struct { + // Represents the observations of a Memcached's current state. + // Memcached.status.conditions.type are: "Available", "Progressing", and "Degraded" + // Memcached.status.conditions.status are one of True, False, Unknown. + // Memcached.status.conditions.reason the value should be a CamelCase string and producers of specific + // condition types may define expected values and meanings for this field, and whether the values + // are considered a guaranteed API. + // Memcached.status.conditions.Message is a human readable message indicating details about the transition. + // For further information see: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Memcached is the Schema for the memcacheds API +type Memcached struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MemcachedSpec `json:"spec,omitempty"` + Status MemcachedStatus `json:"status,omitempty"` + // ControllerManagerConfigurationSpec returns the configurations for controllers + cfg.ControllerManagerConfigurationSpec `json:",inline"` + + ClusterName string `json:"clusterName,omitempty"` +} + +//+kubebuilder:object:root=true + +// MemcachedList contains a list of Memcached +type MemcachedList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Memcached `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) +} diff --git a/docs/book/src/getting-started/testdata/project/api/v1alpha1/zz_generated.deepcopy.go b/docs/book/src/getting-started/testdata/project/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..23d4d278d61 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,123 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024 The Kubernetes authors. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Memcached) DeepCopyInto(out *Memcached) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + in.ControllerManagerConfigurationSpec.DeepCopyInto(&out.ControllerManagerConfigurationSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached. +func (in *Memcached) DeepCopy() *Memcached { + if in == nil { + return nil + } + out := new(Memcached) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Memcached) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedList) DeepCopyInto(out *MemcachedList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Memcached, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList. +func (in *MemcachedList) DeepCopy() *MemcachedList { + if in == nil { + return nil + } + out := new(MemcachedList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MemcachedList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. +func (in *MemcachedSpec) DeepCopy() *MemcachedSpec { + if in == nil { + return nil + } + out := new(MemcachedSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus. +func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { + if in == nil { + return nil + } + out := new(MemcachedStatus) + in.DeepCopyInto(out) + return out +} diff --git a/docs/book/src/getting-started/testdata/project/cmd/main.go b/docs/book/src/getting-started/testdata/project/cmd/main.go new file mode 100644 index 00000000000..d829c4cba1a --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/cmd/main.go @@ -0,0 +1,106 @@ +/* +Copyright 2024 The Kubernetes authors. + +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" + + cachev1alpha1 "example.com/memcached/api/v1alpha1" + "example.com/memcached/internal/controller" + //+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 configFile string + flag.StringVar(&configFile, "config", "", + "The controller will load its initial configuration from this file. "+ + "Omit this flag to use the default configuration values. "+ + "Command-line flags override configuration from this file.") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + var err error + ctrlConfig := cachev1alpha1.Memcached{} + options := ctrl.Options{Scheme: scheme} + if configFile != "" { + options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile).OfKind(&ctrlConfig)) + if err != nil { + setupLog.Error(err, "unable to load the config file") + os.Exit(1) + } + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&controller.MemcachedReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("memcached-controller"), + }).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) + } +} diff --git a/docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml b/docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml new file mode 100644 index 00000000000..63092c98180 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml @@ -0,0 +1,298 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: memcacheds.cache.example.com +spec: + group: cache.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Memcached is the Schema for the memcacheds API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + cacheNamespace: + description: |- + CacheNamespace if specified restricts the manager's cache to watch objects in + the desired namespace Defaults to all namespaces + + + Note: If a namespace is specified, controllers can still Watch for a + cluster-scoped resource (e.g Node). For namespaced resources the cache + will only hold objects from the desired namespace. + type: string + clusterName: + type: string + controller: + description: |- + Controller contains global configuration options for controllers + registered within this manager. + properties: + cacheSyncTimeout: + description: |- + CacheSyncTimeout refers to the time limit set to wait for syncing caches. + Defaults to 2 minutes if not set. + format: int64 + type: integer + groupKindConcurrency: + additionalProperties: + type: integer + description: |- + GroupKindConcurrency is a map from a Kind to the number of concurrent reconciliation + allowed for that controller. + + + When a controller is registered within this manager using the builder utilities, + users have to specify the type the controller reconciles in the For(...) call. + If the object's kind passed matches one of the keys in this map, the concurrency + for that controller is set to the number specified. + + + The key is expected to be consistent in form with GroupKind.String(), + e.g. ReplicaSet in apps group (regardless of version) would be `ReplicaSet.apps`. + type: object + recoverPanic: + description: RecoverPanic indicates if panics should be recovered. + type: boolean + type: object + gracefulShutDown: + description: |- + GracefulShutdownTimeout is the duration given to runnable to stop before the manager actually returns on stop. + To disable graceful shutdown, set to time.Duration(0) + To use graceful shutdown without timeout, set to a negative duration, e.G. time.Duration(-1) + The graceful shutdown is skipped for safety reasons in case the leader election lease is lost. + type: string + health: + description: Health contains the controller health configuration + properties: + healthProbeBindAddress: + description: |- + HealthProbeBindAddress is the TCP address that the controller should bind to + for serving health probes + It can be set to "0" or "" to disable serving the health probe. + type: string + livenessEndpointName: + description: LivenessEndpointName, defaults to "healthz" + type: string + readinessEndpointName: + description: ReadinessEndpointName, defaults to "readyz" + type: string + type: object + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + leaderElection: + description: |- + LeaderElection is the LeaderElection config to be used when configuring + the manager.Manager leader election + properties: + leaderElect: + description: |- + leaderElect enables a leader election client to gain leadership + before executing the main loop. Enable this when running replicated + components for high availability. + type: boolean + leaseDuration: + description: |- + leaseDuration is the duration that non-leader candidates will wait + after observing a leadership renewal until attempting to acquire + leadership of a led but unrenewed leader slot. This is effectively the + maximum duration that a leader can be stopped before it is replaced + by another candidate. This is only applicable if leader election is + enabled. + type: string + renewDeadline: + description: |- + renewDeadline is the interval between attempts by the acting master to + renew a leadership slot before it stops leading. This must be less + than or equal to the lease duration. This is only applicable if leader + election is enabled. + type: string + resourceLock: + description: |- + resourceLock indicates the resource object type that will be used to lock + during leader election cycles. + type: string + resourceName: + description: |- + resourceName indicates the name of resource object that will be used to lock + during leader election cycles. + type: string + resourceNamespace: + description: |- + resourceName indicates the namespace of resource object that will be used to lock + during leader election cycles. + type: string + retryPeriod: + description: |- + retryPeriod is the duration the clients should wait between attempting + acquisition and renewal of a leadership. This is only applicable if + leader election is enabled. + type: string + required: + - leaderElect + - leaseDuration + - renewDeadline + - resourceLock + - resourceName + - resourceNamespace + - retryPeriod + type: object + metadata: + type: object + metrics: + description: Metrics contains the controller metrics configuration + properties: + bindAddress: + description: |- + BindAddress is the TCP address that the controller should bind to + for serving prometheus metrics. + It can be set to "0" to disable the metrics serving. + type: string + type: object + spec: + description: MemcachedSpec defines the desired state of Memcached + properties: + containerPort: + description: Port defines the port that will be used to init the container + with the image + format: int32 + type: integer + size: + description: |- + Size defines the number of Memcached instances + The following markers will use OpenAPI v3 schema to validate the value + More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + format: int32 + maximum: 3 + minimum: 1 + type: integer + type: object + status: + description: MemcachedStatus defines the observed state of Memcached + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + syncPeriod: + description: |- + SyncPeriod determines the minimum frequency at which watched resources are + reconciled. A lower period will correct entropy more quickly, but reduce + responsiveness to change if there are many watched resources. Change this + value only if you know what you are doing. Defaults to 10 hours if unset. + there will a 10 percent jitter between the SyncPeriod of all controllers + so that all controllers will not send list requests simultaneously. + type: string + webhook: + description: Webhook contains the controllers webhook configuration + properties: + certDir: + description: |- + CertDir is the directory that contains the server key and certificate. + if not set, webhook server would look up the server key and certificate in + {TempDir}/k8s-webhook-server/serving-certs. The server key and certificate + must be named tls.key and tls.crt, respectively. + type: string + host: + description: |- + Host is the hostname that the webhook server binds to. + It is used to set webhook.Server.Host. + type: string + port: + description: |- + Port is the port that the webhook server serves at. + It is used to set webhook.Server.Port. + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/docs/book/src/getting-started/testdata/project/config/crd/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/crd/kustomization.yaml new file mode 100644 index 00000000000..5b15e3fc07a --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/crd/kustomization.yaml @@ -0,0 +1,23 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/cache.example.com_memcacheds.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patches: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- path: patches/webhook_in_memcacheds.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- path: patches/cainjection_in_memcacheds.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# [WEBHOOK] To enable webhook, uncomment the following section +# the following config is for teaching kustomize how to do kustomization for CRDs. + +#configurations: +#- kustomizeconfig.yaml diff --git a/docs/book/src/getting-started/testdata/project/config/crd/kustomizeconfig.yaml b/docs/book/src/getting-started/testdata/project/config/crd/kustomizeconfig.yaml new file mode 100644 index 00000000000..ec5c150a9df --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/docs/book/src/getting-started/testdata/project/config/default/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/default/kustomization.yaml new file mode 100644 index 00000000000..e0e588792cf --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/default/kustomization.yaml @@ -0,0 +1,146 @@ +# Adds namespace to all resources. +namespace: project-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: project- + +# Labels to add to all resources and selectors. +#labels: +#- includeSelectors: true +# pairs: +# someName: someValue + +resources: +- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + +patches: +# Protect the /metrics endpoint by putting it behind auth. +# If you want your controller-manager to expose the /metrics +# endpoint w/o any authn/z, please comment the following line. +- path: manager_auth_proxy_patch.yaml + +# Mount the controller config file for loading manager configurations +# through a ComponentConfig type +- path: manager_config_patch.yaml + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- path: manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +#- path: webhookcainjection_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +# Uncomment the following replacements to add the cert-manager CA injection annotations +#replacements: +# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.namespace # namespace of the certificate CR +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - source: # Add cert-manager annotation to the webhook Service +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - source: +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.namespace # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true diff --git a/docs/book/src/getting-started/testdata/project/config/default/manager_auth_proxy_patch.yaml b/docs/book/src/getting-started/testdata/project/config/default/manager_auth_proxy_patch.yaml new file mode 100644 index 00000000000..6f391305524 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,34 @@ +# This patch inject a sidecar container which is a HTTP proxy for the +# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=0" + ports: + - containerPort: 8443 + protocol: TCP + name: https + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi diff --git a/docs/book/src/getting-started/testdata/project/config/default/manager_config_patch.yaml b/docs/book/src/getting-started/testdata/project/config/default/manager_config_patch.yaml new file mode 100644 index 00000000000..6c400155cfb --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/default/manager_config_patch.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + args: + - "--config=controller_manager_config.yaml" + volumeMounts: + - name: manager-config + mountPath: /controller_manager_config.yaml + subPath: controller_manager_config.yaml + volumes: + - name: manager-config + configMap: + name: manager-config diff --git a/docs/book/src/getting-started/testdata/project/config/manager/controller_manager_config.yaml b/docs/book/src/getting-started/testdata/project/config/manager/controller_manager_config.yaml new file mode 100644 index 00000000000..1391a7ad490 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/manager/controller_manager_config.yaml @@ -0,0 +1,20 @@ +apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 +kind: ControllerManagerConfig +metadata: + labels: + app.kubernetes.io/name: controllermanagerconfig + app.kubernetes.io/instance: controller-manager-configuration + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize +health: + healthProbeBindAddress: :8081 +metrics: + bindAddress: 127.0.0.1:8080 +webhook: + port: 9443 +leaderElection: + leaderElect: true + resourceName: 80807133.tutorial.kubebuilder.io +clusterName: example-test diff --git a/docs/book/src/getting-started/testdata/project/config/manager/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/manager/kustomization.yaml new file mode 100644 index 00000000000..2bcd3eeaa94 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/manager/kustomization.yaml @@ -0,0 +1,10 @@ +resources: +- manager.yaml + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- name: manager-config + files: + - controller_manager_config.yaml diff --git a/docs/book/src/getting-started/testdata/project/config/manager/manager.yaml b/docs/book/src/getting-started/testdata/project/config/manager/manager.yaml new file mode 100644 index 00000000000..5117573d611 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/manager/manager.yaml @@ -0,0 +1,103 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: namespace + app.kubernetes.io/instance: system + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app.kubernetes.io/name: deployment + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + # 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. You can + # build your manager image using the makefile target docker-buildx. + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: kubernetes.io/arch + # operator: In + # values: + # - amd64 + # - arm64 + # - ppc64le + # - s390x + # - key: kubernetes.io/os + # operator: In + # values: + # - linux + securityContext: + runAsNonRoot: true + # TODO(user): For common cases that do not require escalating privileges + # it is recommended to ensure that all your Pods/Containers are restrictive. + # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + # Please uncomment the following code if your project does NOT have to work on old Kubernetes + # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). + # seccompProfile: + # type: RuntimeDefault + containers: + - command: + - /manager + image: controller:latest + name: manager + env: + - name: MEMCACHED_IMAGE + value: memcached:1.4.36-alpine + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/docs/book/src/getting-started/testdata/project/config/prometheus/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/prometheus/kustomization.yaml new file mode 100644 index 00000000000..ed137168a1d --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/docs/book/src/getting-started/testdata/project/config/prometheus/monitor.yaml b/docs/book/src/getting-started/testdata/project/config/prometheus/monitor.yaml new file mode 100644 index 00000000000..d67c6106f87 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/prometheus/monitor.yaml @@ -0,0 +1,25 @@ +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: servicemonitor + app.kubernetes.io/instance: controller-manager-metrics-monitor + app.kubernetes.io/component: metrics + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_client_clusterrole.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_client_clusterrole.yaml new file mode 100644 index 00000000000..500386b28f0 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,16 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: metrics-reader + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role.yaml new file mode 100644 index 00000000000..85e39513cc1 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role.yaml @@ -0,0 +1,24 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: proxy-role + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role_binding.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role_binding.yaml new file mode 100644 index 00000000000..8b5ff114fa1 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/instance: proxy-rolebinding + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_service.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_service.yaml new file mode 100644 index 00000000000..f40b3d2c0bd --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/auth_proxy_service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: service + app.kubernetes.io/instance: controller-manager-metrics-service + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/kustomization.yaml new file mode 100644 index 00000000000..731832a6ac3 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/kustomization.yaml @@ -0,0 +1,18 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml +# Comment the following 4 lines if you want to disable +# the auth proxy (https://github.com/brancz/kube-rbac-proxy) +# which protects your /metrics endpoint. +- auth_proxy_service.yaml +- auth_proxy_role.yaml +- auth_proxy_role_binding.yaml +- auth_proxy_client_clusterrole.yaml diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role.yaml new file mode 100644 index 00000000000..1488e1ce2fc --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role.yaml @@ -0,0 +1,44 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: role + app.kubernetes.io/instance: leader-election-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role_binding.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 00000000000..e54e64cda0b --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: rolebinding + app.kubernetes.io/instance: leader-election-rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/memcached_editor_role.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/memcached_editor_role.yaml new file mode 100644 index 00000000000..10f2d2c0b57 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/memcached_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit memcacheds. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: memcached-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: memcached-editor-role +rules: +- apiGroups: + - cache.example.com + resources: + - memcacheds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cache.example.com + resources: + - memcacheds/status + verbs: + - get diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/memcached_viewer_role.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/memcached_viewer_role.yaml new file mode 100644 index 00000000000..b9f2efa5f8e --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/memcached_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view memcacheds. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: memcached-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: memcached-viewer-role +rules: +- apiGroups: + - cache.example.com + resources: + - memcacheds + verbs: + - get + - list + - watch +- apiGroups: + - cache.example.com + resources: + - memcacheds/status + verbs: + - get diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/role.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/role.yaml new file mode 100644 index 00000000000..1e4162fe5c5 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/role.yaml @@ -0,0 +1,59 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manager-role +rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cache.example.com + resources: + - memcacheds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cache.example.com + resources: + - memcacheds/finalizers + verbs: + - update +- apiGroups: + - cache.example.com + resources: + - memcacheds/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/role_binding.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/role_binding.yaml new file mode 100644 index 00000000000..b8189618ab9 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/role_binding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/instance: manager-rolebinding + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/docs/book/src/getting-started/testdata/project/config/rbac/service_account.yaml b/docs/book/src/getting-started/testdata/project/config/rbac/service_account.yaml new file mode 100644 index 00000000000..c38941e4f9e --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/rbac/service_account.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/instance: controller-manager-sa + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml b/docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml new file mode 100644 index 00000000000..e55de91ed15 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml @@ -0,0 +1,11 @@ +apiVersion: cache.example.com/v1alpha1 +kind: Memcached +metadata: + name: memcached-sample +spec: + # TODO(user): edit the following value to ensure the number + # of Pods/Instances your Operand must have on cluster + size: 1 + + # TODO(user): edit the following value to ensure the container has the right port to be initialized + containerPort: 11211 diff --git a/docs/book/src/getting-started/testdata/project/config/samples/kustomization.yaml b/docs/book/src/getting-started/testdata/project/config/samples/kustomization.yaml new file mode 100644 index 00000000000..89d91113a22 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples of your project ## +resources: +- cache_v1alpha1_memcached.yaml +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/docs/book/src/getting-started/testdata/project/go.mod b/docs/book/src/getting-started/testdata/project/go.mod new file mode 100644 index 00000000000..2189db2af92 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/go.mod @@ -0,0 +1,73 @@ +module example.com/memcached + +go 1.21 + +require ( + github.com/onsi/ginkgo/v2 v2.14.0 + github.com/onsi/gomega v1.30.0 + k8s.io/api v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/client-go v0.29.0 + sigs.k8s.io/controller-runtime v0.17.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.16.1 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.29.0 // indirect + k8s.io/component-base v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/docs/book/src/getting-started/testdata/project/go.sum b/docs/book/src/getting-started/testdata/project/go.sum new file mode 100644 index 00000000000..57b4fa9962c --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/go.sum @@ -0,0 +1,205 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= +k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/docs/book/src/getting-started/testdata/project/hack/boilerplate.go.txt b/docs/book/src/getting-started/testdata/project/hack/boilerplate.go.txt new file mode 100644 index 00000000000..0d32012672a --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2024 The Kubernetes authors. + +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. +*/ \ No newline at end of file diff --git a/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go b/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go new file mode 100644 index 00000000000..df2297cd599 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go @@ -0,0 +1,439 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 controller + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + cachev1alpha1 "example.com/memcached/api/v1alpha1" +) + +const memcachedFinalizer = "cache.example.com/finalizer" + +// Definitions to manage status conditions +const ( + // typeAvailableMemcached represents the status of the Deployment reconciliation + typeAvailableMemcached = "Available" + // typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are yet to occur. + typeDegradedMemcached = "Degraded" +) + +// MemcachedReconciler reconciles a Memcached object +type MemcachedReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen +// when the command is executed. +// To know more about markers see: https://book.kubebuilder.io/reference/markers.html + +//+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=create;patch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator +// pattern you will create Controllers which provide a reconcile function +// responsible for synchronizing resources until the desired state is reached on the cluster. +// Breaking this recommendation goes against the design principles of controller-runtime. +// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. +// For further info: +// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ +// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/ +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile +func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + // Fetch the Memcached instance + // The purpose is check if the Custom Resource for the Kind Memcached + // is applied on the cluster if not we return nil to stop the reconciliation + memcached := &cachev1alpha1.Memcached{} + err := r.Get(ctx, req.NamespacedName, memcached) + if err != nil { + if apierrors.IsNotFound(err) { + // If the custom resource is not found then it usually means that it was deleted or not created + // In this way, we will stop the reconciliation + log.Info("memcached resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, "Failed to get memcached") + return ctrl.Result{}, err + } + + // Let's just set the status as Unknown when no status is available + if memcached.Status.Conditions == nil || len(memcached.Status.Conditions) == 0 { + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) + if err = r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + // Let's re-fetch the memcached Custom Resource after updating the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raising the error "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + // if we try to update it again in the following operations + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + } + + // Let's add a finalizer. Then, we can define some operations which should + // occur before the custom resource is deleted. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers + if !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { + log.Info("Adding Finalizer for Memcached") + if ok := controllerutil.AddFinalizer(memcached, memcachedFinalizer); !ok { + log.Error(err, "Failed to add finalizer into the custom resource") + return ctrl.Result{Requeue: true}, nil + } + + if err = r.Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update custom resource to add finalizer") + return ctrl.Result{}, err + } + } + + // Check if the Memcached instance is marked to be deleted, which is + // indicated by the deletion timestamp being set. + isMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil + if isMemcachedMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { + log.Info("Performing Finalizer Operations for Memcached before delete CR") + + // Let's add here a status "Downgrade" to reflect that this resource began its process to be terminated. + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, + Status: metav1.ConditionUnknown, Reason: "Finalizing", + Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", memcached.Name)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + // Perform all operations required before removing the finalizer and allow + // the Kubernetes API to remove the custom resource. + r.doFinalizerOperationsForMemcached(memcached) + + // TODO(user): If you add operations to the doFinalizerOperationsForMemcached method + // then you need to ensure that all worked fine before deleting and updating the Downgrade status + // otherwise, you should requeue here. + + // Re-fetch the memcached Custom Resource before updating the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raising the error "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, + Status: metav1.ConditionTrue, Reason: "Finalizing", + Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", memcached.Name)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + log.Info("Removing Finalizer for Memcached after successfully perform the operations") + if ok := controllerutil.RemoveFinalizer(memcached, memcachedFinalizer); !ok { + log.Error(err, "Failed to remove finalizer for Memcached") + return ctrl.Result{Requeue: true}, nil + } + + if err := r.Update(ctx, memcached); err != nil { + log.Error(err, "Failed to remove finalizer for Memcached") + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } + + // Check if the deployment already exists, if not create a new one + found := &appsv1.Deployment{} + err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + // Define a new deployment + dep, err := r.deploymentForMemcached(memcached) + if err != nil { + log.Error(err, "Failed to define new Deployment resource for Memcached") + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionFalse, Reason: "Reconciling", + Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", memcached.Name, err)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + log.Info("Creating a new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + if err = r.Create(ctx, dep); err != nil { + log.Error(err, "Failed to create new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return ctrl.Result{}, err + } + + // Deployment created successfully + // We will requeue the reconciliation so that we can ensure the state + // and move forward for the next operations + return ctrl.Result{RequeueAfter: time.Minute}, nil + } else if err != nil { + log.Error(err, "Failed to get Deployment") + // Let's return the error for the reconciliation be re-trigged again + return ctrl.Result{}, err + } + + // The CRD API defines that the Memcached type have a MemcachedSpec.Size field + // to set the quantity of Deployment instances to the desired state on the cluster. + // Therefore, the following code will ensure the Deployment size is the same as defined + // via the Size spec of the Custom Resource which we are reconciling. + size := memcached.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + if err = r.Update(ctx, found); err != nil { + log.Error(err, "Failed to update Deployment", + "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + + // Re-fetch the memcached Custom Resource before updating the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raising the error "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionFalse, Reason: "Resizing", + Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", memcached.Name, err)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + // Now, that we update the size we want to requeue the reconciliation + // so that we can ensure that we have the latest state of the resource before + // update. Also, it will help ensure the desired state on the cluster + return ctrl.Result{Requeue: true}, nil + } + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionTrue, Reason: "Reconciling", + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", memcached.Name, size)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// finalizeMemcached will perform the required operations before delete the CR. +func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *cachev1alpha1.Memcached) { + // TODO(user): Add the cleanup steps that the operator + // needs to do before the CR can be deleted. Examples + // of finalizers include performing backups and deleting + // resources that are not owned by this CR, like a PVC. + + // Note: It is not recommended to use finalizers with the purpose of deleting resources which are + // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, + // are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference. + // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. + // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ + + // The following implementation will raise an event + r.Recorder.Event(cr, "Warning", "Deleting", + fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", + cr.Name, + cr.Namespace)) +} + +// deploymentForMemcached returns a Memcached Deployment object +func (r *MemcachedReconciler) deploymentForMemcached( + memcached *cachev1alpha1.Memcached) (*appsv1.Deployment, error) { + 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, + }, + 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{ + RunAsNonRoot: &[]bool{true}[0], + 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"}, + }}, + }, + }, + }, + } + + // Set the ownerRef for the Deployment + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ + if err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil { + return nil, err + } + return dep, nil +} + +// labelsForMemcached returns the labels for selecting the resources +// 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": "project", + "app.kubernetes.io/created-by": "controller-manager", + } +} + +// 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 "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + } + return image, nil +} + +// SetupWithManager sets up the controller with the Manager. +// Note that the Deployment will be also watched in order to ensure its +// desirable state on the cluster +func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&cachev1alpha1.Memcached{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} diff --git a/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go b/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go new file mode 100644 index 00000000000..66b20cf0d57 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go @@ -0,0 +1,154 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 controller + +import ( + "context" + "fmt" + "os" + "time" + + //nolint:golint + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + cachev1alpha1 "example.com/memcached/api/v1alpha1" +) + +var _ = Describe("Memcached controller", func() { + Context("Memcached controller test", func() { + + const MemcachedName = "test-memcached" + + ctx := context.Background() + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: MemcachedName, + Namespace: MemcachedName, + }, + } + + typeNamespaceName := types.NamespacedName{ + Name: MemcachedName, + Namespace: MemcachedName, + } + memcached := &cachev1alpha1.Memcached{} + + BeforeEach(func() { + By("Creating the Namespace to perform the tests") + err := k8sClient.Create(ctx, namespace) + Expect(err).To(Not(HaveOccurred())) + + By("Setting the Image ENV VAR which stores the Operand image") + err = os.Setenv("MEMCACHED_IMAGE", "example.com/image:test") + Expect(err).To(Not(HaveOccurred())) + + By("creating the custom resource for the Kind Memcached") + err = k8sClient.Get(ctx, typeNamespaceName, memcached) + if err != nil && errors.IsNotFound(err) { + // Let's mock our custom resource at the same way that we would + // apply on the cluster the manifest under config/samples + memcached := &cachev1alpha1.Memcached{ + ObjectMeta: metav1.ObjectMeta{ + Name: MemcachedName, + Namespace: namespace.Name, + }, + Spec: cachev1alpha1.MemcachedSpec{ + Size: 1, + ContainerPort: 11211, + }, + } + + err = k8sClient.Create(ctx, memcached) + Expect(err).To(Not(HaveOccurred())) + } + }) + + AfterEach(func() { + By("removing the custom resource for the Kind Memcached") + found := &cachev1alpha1.Memcached{} + err := k8sClient.Get(ctx, typeNamespaceName, found) + Expect(err).To(Not(HaveOccurred())) + + Eventually(func() error { + return k8sClient.Delete(context.TODO(), found) + }, 2*time.Minute, time.Second).Should(Succeed()) + + // TODO(user): Attention if you improve this code by adding other context test you MUST + // be aware of the current delete namespace limitations. + // More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations + By("Deleting the Namespace to perform the tests") + _ = k8sClient.Delete(ctx, namespace) + + By("Removing the Image ENV VAR which stores the Operand image") + _ = os.Unsetenv("MEMCACHED_IMAGE") + }) + + It("should successfully reconcile a custom resource for Memcached", func() { + By("Checking if the custom resource was successfully created") + Eventually(func() error { + found := &cachev1alpha1.Memcached{} + return k8sClient.Get(ctx, typeNamespaceName, found) + }, time.Minute, time.Second).Should(Succeed()) + + By("Reconciling the custom resource created") + memcachedReconciler := &MemcachedReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := memcachedReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespaceName, + }) + Expect(err).To(Not(HaveOccurred())) + + By("Checking if Deployment was successfully created in the reconciliation") + Eventually(func() error { + found := &appsv1.Deployment{} + return k8sClient.Get(ctx, typeNamespaceName, found) + }, time.Minute, time.Second).Should(Succeed()) + + By("Checking the latest Status Condition added to the Memcached instance") + Eventually(func() error { + if memcached.Status.Conditions != nil && + len(memcached.Status.Conditions) != 0 { + latestStatusCondition := memcached.Status.Conditions[len(memcached.Status.Conditions)-1] + expectedLatestStatusCondition := metav1.Condition{ + Type: typeAvailableMemcached, + Status: metav1.ConditionTrue, + Reason: "Reconciling", + Message: fmt.Sprintf( + "Deployment for custom resource (%s) with %d replicas created successfully", + memcached.Name, + memcached.Spec.Size), + } + if latestStatusCondition != expectedLatestStatusCondition { + return fmt.Errorf("The latest status condition added to the Memcached instance is not as expected") + } + } + return nil + }, time.Minute, time.Second).Should(Succeed()) + }) + }) +}) diff --git a/docs/book/src/getting-started/testdata/project/internal/controller/suite_test.go b/docs/book/src/getting-started/testdata/project/internal/controller/suite_test.go new file mode 100644 index 00000000000..baccfcfac33 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/internal/controller/suite_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 controller + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + cachev1alpha1 "example.com/memcached/api/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = cachev1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/docs/book/src/getting-started/testdata/project/test/e2e/e2e_suite_test.go b/docs/book/src/getting-started/testdata/project/test/e2e/e2e_suite_test.go new file mode 100644 index 00000000000..a2d85acad7b --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/test/e2e/e2e_suite_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 e2e + +import ( + "fmt" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// Run e2e tests using the Ginkgo runner. +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + fmt.Fprintf(GinkgoWriter, "Starting project suite\n") + RunSpecs(t, "e2e suite") +} diff --git a/docs/book/src/getting-started/testdata/project/test/e2e/e2e_test.go b/docs/book/src/getting-started/testdata/project/test/e2e/e2e_test.go new file mode 100644 index 00000000000..e364a6fd139 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/test/e2e/e2e_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 e2e + +import ( + "fmt" + "os/exec" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "example.com/memcached/test/utils" +) + +const namespace = "project-system" + +var _ = Describe("controller", Ordered, func() { + BeforeAll(func() { + By("installing prometheus operator") + Expect(utils.InstallPrometheusOperator()).To(Succeed()) + + By("installing the cert-manager") + Expect(utils.InstallCertManager()).To(Succeed()) + + By("creating manager namespace") + cmd := exec.Command("kubectl", "create", "ns", namespace) + _, _ = utils.Run(cmd) + }) + + AfterAll(func() { + By("uninstalling the Prometheus manager bundle") + utils.UninstallPrometheusOperator() + + By("uninstalling the cert-manager bundle") + utils.UninstallCertManager() + + By("removing manager namespace") + cmd := exec.Command("kubectl", "delete", "ns", namespace) + _, _ = utils.Run(cmd) + }) + + Context("Operator", func() { + It("should run successfully", func() { + var controllerPodName string + var err error + + // projectimage stores the name of the image used in the example + var projectimage = "example.com/project:v0.0.1" + + By("building the manager(Operator) image") + cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage)) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("loading the the manager(Operator) image on Kind") + err = utils.LoadImageToKindClusterWithName(projectimage) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("installing CRDs") + cmd = exec.Command("make", "install") + _, err = utils.Run(cmd) + + By("deploying the controller-manager") + cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage)) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("validating that the controller-manager pod is running as expected") + verifyControllerUp := func() error { + // Get pod name + + cmd = exec.Command("kubectl", "get", + "pods", "-l", "control-plane=controller-manager", + "-o", "go-template={{ range .items }}"+ + "{{ if not .metadata.deletionTimestamp }}"+ + "{{ .metadata.name }}"+ + "{{ \"\\n\" }}{{ end }}{{ end }}", + "-n", namespace, + ) + + podOutput, err := utils.Run(cmd) + ExpectWithOffset(2, err).NotTo(HaveOccurred()) + podNames := utils.GetNonEmptyLines(string(podOutput)) + if len(podNames) != 1 { + return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames)) + } + controllerPodName = podNames[0] + ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("controller-manager")) + + // Validate pod status + cmd = exec.Command("kubectl", "get", + "pods", controllerPodName, "-o", "jsonpath={.status.phase}", + "-n", namespace, + ) + status, err := utils.Run(cmd) + ExpectWithOffset(2, err).NotTo(HaveOccurred()) + if string(status) != "Running" { + return fmt.Errorf("controller pod in %s status", status) + } + return nil + } + EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed()) + + }) + }) +}) diff --git a/docs/book/src/getting-started/testdata/project/test/utils/utils.go b/docs/book/src/getting-started/testdata/project/test/utils/utils.go new file mode 100644 index 00000000000..0c91bc93d22 --- /dev/null +++ b/docs/book/src/getting-started/testdata/project/test/utils/utils.go @@ -0,0 +1,140 @@ +/* +Copyright 2024 The Kubernetes authors. + +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 utils + +import ( + "fmt" + "os" + "os/exec" + "strings" + + . "github.com/onsi/ginkgo/v2" //nolint:golint,revive +) + +const ( + prometheusOperatorVersion = "v0.68.0" + prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + + "releases/download/%s/bundle.yaml" + + certmanagerVersion = "v1.5.3" + certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml" +) + +func warnError(err error) { + fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) +} + +// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. +func InstallPrometheusOperator() error { + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) + cmd := exec.Command("kubectl", "create", "-f", url) + _, err := Run(cmd) + return err +} + +// Run executes the provided command within this context +func Run(cmd *exec.Cmd) ([]byte, error) { + dir, _ := GetProjectDir() + cmd.Dir = dir + + if err := os.Chdir(cmd.Dir); err != nil { + fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + } + + cmd.Env = append(os.Environ(), "GO111MODULE=on") + command := strings.Join(cmd.Args, " ") + fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + output, err := cmd.CombinedOutput() + if err != nil { + return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + } + + return output, nil +} + +// UninstallPrometheusOperator uninstalls the prometheus +func UninstallPrometheusOperator() { + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) + cmd := exec.Command("kubectl", "delete", "-f", url) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// UninstallCertManager uninstalls the cert manager +func UninstallCertManager() { + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) + cmd := exec.Command("kubectl", "delete", "-f", url) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// InstallCertManager installs the cert manager bundle. +func InstallCertManager() error { + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) + cmd := exec.Command("kubectl", "apply", "-f", url) + if _, err := Run(cmd); err != nil { + return err + } + // Wait for cert-manager-webhook to be ready, which can take time if cert-manager + // was re-installed after uninstalling on a cluster. + cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", + "--for", "condition=Available", + "--namespace", "cert-manager", + "--timeout", "5m", + ) + + _, err := Run(cmd) + return err +} + +// LoadImageToKindCluster loads a local docker image to the kind cluster +func LoadImageToKindClusterWithName(name string) error { + cluster := "kind" + if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { + cluster = v + } + kindOptions := []string{"load", "docker-image", name, "--name", cluster} + cmd := exec.Command("kind", kindOptions...) + _, err := Run(cmd) + return err +} + +// GetNonEmptyLines converts given command output string into individual objects +// according to line breakers, and ignores the empty elements in it. +func GetNonEmptyLines(output string) []string { + var res []string + elements := strings.Split(output, "\n") + for _, element := range elements { + if element != "" { + res = append(res, element) + } + } + + return res +} + +// GetProjectDir will return the directory where the project is +func GetProjectDir() (string, error) { + wd, err := os.Getwd() + if err != nil { + return wd, err + } + wd = strings.Replace(wd, "/test/e2e", "", -1) + return wd, nil +} diff --git a/hack/docs/generate_samples.go b/hack/docs/generate_samples.go index aa25b687dd1..48b23c904fb 100644 --- a/hack/docs/generate_samples.go +++ b/hack/docs/generate_samples.go @@ -21,47 +21,62 @@ import ( componentconfig "sigs.k8s.io/kubebuilder/v3/hack/docs/internal/component-config-tutorial" cronjob "sigs.k8s.io/kubebuilder/v3/hack/docs/internal/cronjob-tutorial" + gettingstarted "sigs.k8s.io/kubebuilder/v3/hack/docs/internal/getting-started" ) // Make sure executing `build_kb` to generate kb executable from the source code const KubebuilderBinName = "/tmp/kubebuilder/bin/kubebuilder" +type tutorial_generator interface { + Prepare() + GenerateSampleProject() + UpdateTutorial() + CodeGen() +} + func main() { + type generator func() + + // TODO: Generate multiversion-tutorial + tutorials := map[string]generator{ + "component-config": UpdateComponentConfigTutorial, + "cronjob": UpdateCronjobTutorial, + "getting-started": UpdateGettingStarted, + } + log.SetFormatter(&log.TextFormatter{DisableTimestamp: true}) log.Println("Generating documents...") - log.Println("Generating component-config tutorial...") - UpdateComponentConfigTutorial() - - log.Println("Generating cronjob tutorial...") - UpdateCronjobTutorial() - // TODO: Generate multiversion-tutorial + for tutorial, updater := range tutorials { + log.Printf("Generating %s tutorial\n", tutorial) + updater() + } } -func UpdateComponentConfigTutorial() { - binaryPath := KubebuilderBinName - samplePath := "docs/book/src/component-config-tutorial/testdata/project/" +func updateTutorial(generator tutorial_generator) { + generator.Prepare() - sp := componentconfig.NewSample(binaryPath, samplePath) + generator.GenerateSampleProject() - sp.Prepare() + generator.UpdateTutorial() - sp.GenerateSampleProject() - - sp.UpdateTutorial() + generator.CodeGen() +} - sp.CodeGen() +func UpdateComponentConfigTutorial() { + samplePath := "docs/book/src/component-config-tutorial/testdata/project/" + sp := componentconfig.NewSample(KubebuilderBinName, samplePath) + updateTutorial(&sp) } func UpdateCronjobTutorial() { - binaryPath := KubebuilderBinName samplePath := "docs/book/src/cronjob-tutorial/testdata/project/" + sp := cronjob.NewSample(KubebuilderBinName, samplePath) + updateTutorial(&sp) +} - sp := cronjob.NewSample(binaryPath, samplePath) - - sp.Prepare() - - sp.GenerateSampleProject() - - sp.UpdateTutorial() +func UpdateGettingStarted() { + samplePath := "docs/book/src/getting-started/testdata/project/" + sp := gettingstarted.NewSample(KubebuilderBinName, samplePath) + updateTutorial(&sp) } diff --git a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go index 119c8526f88..0039c0454c4 100644 --- a/hack/docs/internal/cronjob-tutorial/generate_cronjob.go +++ b/hack/docs/internal/cronjob-tutorial/generate_cronjob.go @@ -123,6 +123,11 @@ func (sp *Sample) UpdateTutorial() { addControllerTest(sp) } +// CodeGen is a noop for this sample, just to make generation of all samples +// more efficient. We may want to refactor `UpdateTutorial` some day to take +// advantage of a separate call, but it is not necessary. +func (sp *Sample) CodeGen() {} + func codeGen(sp *Sample) { cmd := exec.Command("go", "get", "github.com/robfig/cron") _, err := sp.ctx.Run(cmd) diff --git a/hack/docs/internal/getting-started/generate_getting_started.go b/hack/docs/internal/getting-started/generate_getting_started.go new file mode 100644 index 00000000000..cccbb246fed --- /dev/null +++ b/hack/docs/internal/getting-started/generate_getting_started.go @@ -0,0 +1,177 @@ +/* +Copyright 2024 The Kubernetes Authors. + +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 gettingstarted + +import ( + "os" + "os/exec" + "path/filepath" + + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + "sigs.k8s.io/kubebuilder/v3/test/e2e/utils" +) + +type Sample struct { + ctx *utils.TestContext +} + +func NewSample(binaryPath, samplePath string) Sample { + log.Infof("Generating the sample context of getting-started...") + + ctx := newSampleContext(binaryPath, samplePath, "GO111MODULE=on") + + return Sample{&ctx} +} + +func newSampleContext(binaryPath string, samplePath string, env ...string) utils.TestContext { + cmdContext := &utils.CmdContext{ + Env: env, + Dir: samplePath, + } + + testContext := utils.TestContext{ + CmdContext: cmdContext, + BinaryName: binaryPath, + } + + return testContext +} + +// Prepare the Context for the sample project +func (sp *Sample) Prepare() { + log.Infof("destroying directory for getting-started sample project") + sp.ctx.Destroy() + + log.Infof("refreshing tools and creating directory...") + err := sp.ctx.Prepare() + + CheckError("creating directory for sample project", err) +} + +func (sp *Sample) GenerateSampleProject() { + log.Infof("Initializing the getting started project") + err := sp.ctx.Init( + "--domain", "example.com", + "--repo", "example.com/memcached", + "--license", "apache2", + "--owner", "The Kubernetes authors", + "--plugins=go/v4", + "--component-config", + ) + CheckError("Initializing the getting started project", err) + + log.Infof("Adding a new config type") + err = sp.ctx.CreateAPI( + "--group", "cache", + "--version", "v1alpha1", + "--kind", "Memcached", + "--image", "memcached:1.4.36-alpine", + "--image-container-command", "memcached,-m=64,-o,modern,-v", + "--image-container-port", "11211", + "--run-as-user", "1001", + "--plugins", "deploy-image/v1-alpha", + "--make=false", + ) + CheckError("Creating the API", err) +} + +func (sp *Sample) UpdateTutorial() { + // 1. generate controller_manager_config.yaml + var fs = afero.NewOsFs() + err := afero.WriteFile(fs, filepath.Join(sp.ctx.Dir, "config/manager/controller_manager_config.yaml"), + []byte(`apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 +kind: ControllerManagerConfig +metadata: + labels: + app.kubernetes.io/name: controllermanagerconfig + app.kubernetes.io/instance: controller-manager-configuration + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: project + app.kubernetes.io/part-of: project + app.kubernetes.io/managed-by: kustomize +health: + healthProbeBindAddress: :8081 +metrics: + bindAddress: 127.0.0.1:8080 +webhook: + port: 9443 +leaderElection: + leaderElect: true + resourceName: 80807133.tutorial.kubebuilder.io +clusterName: example-test +`), 0600) + CheckError("fixing controller_manager_config", err) + + // 2. fix memcached_types.go + err = pluginutil.InsertCode( + filepath.Join(sp.ctx.Dir, "api/v1alpha1/memcached_types.go"), + `metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"`, + ` + cfg "sigs.k8s.io/controller-runtime/pkg/config/v1alpha1"`) + CheckError("fixing memcached_types", err) + + err = pluginutil.InsertCode( + filepath.Join(sp.ctx.Dir, "api/v1alpha1/memcached_types.go"), + `Status MemcachedStatus `+"`"+`json:"status,omitempty"`+"`", + ` + // ControllerManagerConfigurationSpec returns the configurations for controllers + cfg.ControllerManagerConfigurationSpec `+"`"+`json:",inline"`+"`"+` + + ClusterName string `+"`"+`json:"clusterName,omitempty"`+"`", + ) + + CheckError("fixing memcached_types", err) + + // 3. fix main + err = pluginutil.InsertCode( + filepath.Join(sp.ctx.Dir, "cmd/main.go"), + `var err error`, + ` + ctrlConfig := cachev1alpha1.Memcached{}`) + CheckError("fixing main.go", err) + + err = pluginutil.InsertCode( + filepath.Join(sp.ctx.Dir, "cmd/main.go"), + `AtPath(configFile)`, + `.OfKind(&ctrlConfig)`) + CheckError("fixing main.go", err) +} + +func (sp *Sample) CodeGen() { + + cmd := exec.Command("make", "manifests") + _, err := sp.ctx.Run(cmd) + CheckError("Failed to run make manifests for getting started tutorial", err) + + cmd = exec.Command("make", "all") + _, err = sp.ctx.Run(cmd) + CheckError("Failed to run make all for getting started tutorial", err) + + cmd = exec.Command("go", "mod", "tidy") + _, err = sp.ctx.Run(cmd) + CheckError("Failed to run go mod tidy all for getting started tutorial", err) +} + +// CheckError will exit with exit code 1 when err is not nil. +func CheckError(msg string, err error) { + if err != nil { + log.Errorf("error %s: %s", msg, err) + os.Exit(1) + } +} diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go index d7e6f9262af..9ae18ab927b 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go @@ -113,7 +113,7 @@ const {{ lower .Resource.Kind }}Finalizer = "{{ .Resource.Group }}.{{ .Resource. const ( // typeAvailable{{ .Resource.Kind }} represents the status of the Deployment reconciliation typeAvailable{{ .Resource.Kind }} = "Available" - // typeDegraded{{ .Resource.Kind }} represents the status used when the custom resource is deleted and the finalizer operations are must to occur. + // typeDegraded{{ .Resource.Kind }} represents the status used when the custom resource is deleted and the finalizer operations are yet to occur. typeDegraded{{ .Resource.Kind }} = "Degraded" ) @@ -125,7 +125,7 @@ type {{ .Resource.Kind }}Reconciler struct { } // The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen -// when the command is executed. +// when the command is executed. // To know more about markers see: https://book.kubebuilder.io/reference/markers.html //+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete @@ -137,10 +137,10 @@ type {{ .Resource.Kind }}Reconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator +// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator // pattern you will create Controllers which provide a reconcile function -// responsible for synchronizing resources until the desired state is reached on the cluster. -// Breaking this recommendation goes against the design principles of controller-runtime. +// responsible for synchronizing resources until the desired state is reached on the cluster. +// Breaking this recommendation goes against the design principles of controller-runtime. // and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. // For further info: // - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ @@ -156,8 +156,8 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}) if err != nil { if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created - // In this way, we will stop the reconciliation + // If the custom resource is not found then it usually means that it was deleted or not created + // In this way, we will stop the reconciliation log.Info("{{ lower .Resource.Kind }} resource not found. Ignoring since object must be deleted") return ctrl.Result{}, nil } @@ -166,7 +166,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } - // Let's just set the status as Unknown when no status are available + // Let's just set the status as Unknown when no status is available if {{ lower .Resource.Kind }}.Status.Conditions == nil || len({{ lower .Resource.Kind }}.Status.Conditions) == 0 { meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }}, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) if err = r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil { @@ -174,9 +174,9 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } - // Let's re-fetch the {{ lower .Resource.Kind }} Custom Resource after update the status + // Let's re-fetch the {{ lower .Resource.Kind }} Custom Resource after updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation // if we try to update it again in the following operations if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil { @@ -186,7 +186,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl } // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. + // occur before the custom resource is deleted. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) { log.Info("Adding Finalizer for {{ .Resource.Kind }}") @@ -208,7 +208,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl if controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) { log.Info("Performing Finalizer Operations for {{ .Resource.Kind }} before delete CR") - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + // Let's add here a status "Downgrade" to reflect that this resource began its process to be terminated. meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeDegraded{{ .Resource.Kind }}, Status: metav1.ConditionUnknown, Reason: "Finalizing", Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", {{ lower .Resource.Kind }}.Name)}) @@ -218,17 +218,17 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } - // Perform all operations required before remove the finalizer and allow + // Perform all operations required before removing the finalizer and allow // the Kubernetes API to remove the custom resource. r.doFinalizerOperationsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }}) - // TODO(user): If you add operations to the doFinalizerOperationsFor{{ .Resource.Kind }} method + // TODO(user): If you add operations to the doFinalizerOperationsFor{{ .Resource.Kind }} method // then you need to ensure that all worked fine before deleting and updating the Downgrade status // otherwise, you should requeue here. - // Re-fetch the {{ lower .Resource.Kind }} Custom Resource before update the status + // Re-fetch the {{ lower .Resource.Kind }} Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil { log.Error(err, "Failed to re-fetch {{ lower .Resource.Kind }}") @@ -280,7 +280,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } - log.Info("Creating a new Deployment", + log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) if err = r.Create(ctx, dep); err != nil { log.Error(err, "Failed to create new Deployment", @@ -288,30 +288,30 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } - // Deployment created successfully + // Deployment created successfully // We will requeue the reconciliation so that we can ensure the state // and move forward for the next operations return ctrl.Result{RequeueAfter: time.Minute}, nil } else if err != nil { log.Error(err, "Failed to get Deployment") - // Let's return the error for the reconciliation be re-trigged again + // Let's return the error for the reconciliation be re-trigged again return ctrl.Result{}, err } - // The CRD API is defining that the {{ .Resource.Kind }} type, have a {{ .Resource.Kind }}Spec.Size field - // to set the quantity of Deployment instances is the desired state on the cluster. - // Therefore, the following code will ensure the Deployment size is the same as defined + // The CRD API defines that the {{ .Resource.Kind }} type have a {{ .Resource.Kind }}Spec.Size field + // to set the quantity of Deployment instances to the desired state on the cluster. + // Therefore, the following code will ensure the Deployment size is the same as defined // via the Size spec of the Custom Resource which we are reconciling. size := {{ lower .Resource.Kind }}.Spec.Size if *found.Spec.Replicas != size { found.Spec.Replicas = &size if err = r.Update(ctx, found); err != nil { - log.Error(err, "Failed to update Deployment", + log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - // Re-fetch the {{ lower .Resource.Kind }} Custom Resource before update the status + // Re-fetch the {{ lower .Resource.Kind }} Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil { log.Error(err, "Failed to re-fetch {{ lower .Resource.Kind }}") @@ -357,9 +357,9 @@ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Ki // of finalizers include performing backups and deleting // resources that are not owned by this CR, like a PVC. - // Note: It is not recommended to use finalizers with the purpose of delete resources which are - // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, - // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. + // Note: It is not recommended to use finalizers with the purpose of deleting resources which are + // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, + // are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference. // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ @@ -476,7 +476,7 @@ func imageFor{{ .Resource.Kind }}() (string, error) { } // SetupWithManager sets up the controller with the Manager. -// Note that the Deployment will be also watched in order to ensure its +// Note that the Deployment will be also watched in order to ensure its // desirable state on the cluster func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/testdata/project-v4-with-deploy-image/internal/controller/busybox_controller.go b/testdata/project-v4-with-deploy-image/internal/controller/busybox_controller.go index d8d299a90fa..95a3a1807b0 100644 --- a/testdata/project-v4-with-deploy-image/internal/controller/busybox_controller.go +++ b/testdata/project-v4-with-deploy-image/internal/controller/busybox_controller.go @@ -45,7 +45,7 @@ const busyboxFinalizer = "example.com.testproject.org/finalizer" const ( // typeAvailableBusybox represents the status of the Deployment reconciliation typeAvailableBusybox = "Available" - // typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are must to occur. + // typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are yet to occur. typeDegradedBusybox = "Degraded" ) @@ -88,7 +88,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct err := r.Get(ctx, req.NamespacedName, busybox) if err != nil { if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created + // If the custom resource is not found then it usually means that it was deleted or not created // In this way, we will stop the reconciliation log.Info("busybox resource not found. Ignoring since object must be deleted") return ctrl.Result{}, nil @@ -98,7 +98,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // Let's just set the status as Unknown when no status are available + // Let's just set the status as Unknown when no status is available if busybox.Status.Conditions == nil || len(busybox.Status.Conditions) == 0 { meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) if err = r.Status().Update(ctx, busybox); err != nil { @@ -106,9 +106,9 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // Let's re-fetch the busybox Custom Resource after update the status + // Let's re-fetch the busybox Custom Resource after updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation // if we try to update it again in the following operations if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { @@ -118,7 +118,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. + // occur before the custom resource is deleted. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { log.Info("Adding Finalizer for Busybox") @@ -140,7 +140,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { log.Info("Performing Finalizer Operations for Busybox before delete CR") - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + // Let's add here a status "Downgrade" to reflect that this resource began its process to be terminated. meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox, Status: metav1.ConditionUnknown, Reason: "Finalizing", Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", busybox.Name)}) @@ -150,7 +150,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // Perform all operations required before remove the finalizer and allow + // Perform all operations required before removing the finalizer and allow // the Kubernetes API to remove the custom resource. r.doFinalizerOperationsForBusybox(busybox) @@ -158,9 +158,9 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // then you need to ensure that all worked fine before deleting and updating the Downgrade status // otherwise, you should requeue here. - // Re-fetch the busybox Custom Resource before update the status + // Re-fetch the busybox Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { log.Error(err, "Failed to re-fetch busybox") @@ -230,8 +230,8 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // The CRD API is defining that the Busybox type, have a BusyboxSpec.Size field - // to set the quantity of Deployment instances is the desired state on the cluster. + // The CRD API defines that the Busybox type have a BusyboxSpec.Size field + // to set the quantity of Deployment instances to the desired state on the cluster. // Therefore, the following code will ensure the Deployment size is the same as defined // via the Size spec of the Custom Resource which we are reconciling. size := busybox.Spec.Size @@ -241,9 +241,9 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - // Re-fetch the busybox Custom Resource before update the status + // Re-fetch the busybox Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { log.Error(err, "Failed to re-fetch busybox") @@ -289,9 +289,9 @@ func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alph // of finalizers include performing backups and deleting // resources that are not owned by this CR, like a PVC. - // Note: It is not recommended to use finalizers with the purpose of delete resources which are + // Note: It is not recommended to use finalizers with the purpose of deleting resources which are // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, - // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. + // are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference. // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ diff --git a/testdata/project-v4-with-deploy-image/internal/controller/memcached_controller.go b/testdata/project-v4-with-deploy-image/internal/controller/memcached_controller.go index c662113e13a..593332c308f 100644 --- a/testdata/project-v4-with-deploy-image/internal/controller/memcached_controller.go +++ b/testdata/project-v4-with-deploy-image/internal/controller/memcached_controller.go @@ -45,7 +45,7 @@ const memcachedFinalizer = "example.com.testproject.org/finalizer" const ( // typeAvailableMemcached represents the status of the Deployment reconciliation typeAvailableMemcached = "Available" - // typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are must to occur. + // typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are yet to occur. typeDegradedMemcached = "Degraded" ) @@ -88,7 +88,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( err := r.Get(ctx, req.NamespacedName, memcached) if err != nil { if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created + // If the custom resource is not found then it usually means that it was deleted or not created // In this way, we will stop the reconciliation log.Info("memcached resource not found. Ignoring since object must be deleted") return ctrl.Result{}, nil @@ -98,7 +98,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Let's just set the status as Unknown when no status are available + // Let's just set the status as Unknown when no status is available if memcached.Status.Conditions == nil || len(memcached.Status.Conditions) == 0 { meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) if err = r.Status().Update(ctx, memcached); err != nil { @@ -106,9 +106,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Let's re-fetch the memcached Custom Resource after update the status + // Let's re-fetch the memcached Custom Resource after updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation // if we try to update it again in the following operations if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { @@ -118,7 +118,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. + // occur before the custom resource is deleted. // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { log.Info("Adding Finalizer for Memcached") @@ -140,7 +140,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( if controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { log.Info("Performing Finalizer Operations for Memcached before delete CR") - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + // Let's add here a status "Downgrade" to reflect that this resource began its process to be terminated. meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, Status: metav1.ConditionUnknown, Reason: "Finalizing", Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", memcached.Name)}) @@ -150,7 +150,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // Perform all operations required before remove the finalizer and allow + // Perform all operations required before removing the finalizer and allow // the Kubernetes API to remove the custom resource. r.doFinalizerOperationsForMemcached(memcached) @@ -158,9 +158,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // then you need to ensure that all worked fine before deleting and updating the Downgrade status // otherwise, you should requeue here. - // Re-fetch the memcached Custom Resource before update the status + // Re-fetch the memcached Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { log.Error(err, "Failed to re-fetch memcached") @@ -230,8 +230,8 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - // The CRD API is defining that the Memcached type, have a MemcachedSpec.Size field - // to set the quantity of Deployment instances is the desired state on the cluster. + // The CRD API defines that the Memcached type have a MemcachedSpec.Size field + // to set the quantity of Deployment instances to the desired state on the cluster. // Therefore, the following code will ensure the Deployment size is the same as defined // via the Size spec of the Custom Resource which we are reconciling. size := memcached.Spec.Size @@ -241,9 +241,9 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - // Re-fetch the memcached Custom Resource before update the status + // Re-fetch the memcached Custom Resource before updating the status // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply + // raising the error "the object has been modified, please apply // your changes to the latest version and try again" which would re-trigger the reconciliation if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { log.Error(err, "Failed to re-fetch memcached") @@ -289,9 +289,9 @@ func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1 // of finalizers include performing backups and deleting // resources that are not owned by this CR, like a PVC. - // Note: It is not recommended to use finalizers with the purpose of delete resources which are + // Note: It is not recommended to use finalizers with the purpose of deleting resources which are // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, - // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. + // are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference. // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/