diff --git a/.github/workflows/apidiff.yml b/.github/workflows/apidiff.yml index c9752d9cdd9..2c4fb2c4957 100644 --- a/.github/workflows/apidiff.yml +++ b/.github/workflows/apidiff.yml @@ -24,7 +24,7 @@ jobs: with: go-version-file: go.mod - name: Execute go-apidiff - uses: joelanford/go-apidiff@v0.8.2 + uses: joelanford/go-apidiff@v0.8.3 with: compare-imports: true print-compatible: true diff --git a/.github/workflows/external-plugin.yml b/.github/workflows/external-plugin.yml index 71d6e04ee1d..2c33b3f5b62 100644 --- a/.github/workflows/external-plugin.yml +++ b/.github/workflows/external-plugin.yml @@ -18,57 +18,14 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository steps: - - name: Clone the code + - name: Checkout repository uses: actions/checkout@v4 - with: - fetch-depth: 1 # Minimal history to avoid .git permissions issues - name: Setup Go uses: actions/setup-go@v5 with: - go-version-file: docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/go.mod - - - name: Build Sample External Plugin - working-directory: docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1 - run: | - mkdir -p ./bin - make build - - - name: Move Plugin Binary to Plugin Path - run: | - # Define the plugin destination for Linux (XDG_CONFIG_HOME path) - XDG_CONFIG_HOME="${HOME}/.config" - PLUGIN_DEST="$XDG_CONFIG_HOME/kubebuilder/plugins/sampleexternalplugin/v1" - - # Ensure destination exists and move the built binary - mkdir -p "$PLUGIN_DEST" - mv docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/bin/sampleexternalplugin "$PLUGIN_DEST/sampleexternalplugin" - chmod +x "$PLUGIN_DEST/sampleexternalplugin" # Ensure the binary is executable - - - name: Build Kubebuilder Binary and Setup Environment - env: - KUBEBUILDER_ASSETS: $GITHUB_WORKSPACE/bin - run: | - # Build Kubebuilder Binary - export kb_root_dir=$(pwd) - go build -o "${kb_root_dir}/bin/kubebuilder" ./cmd - chmod +x "${kb_root_dir}/bin/kubebuilder" # Ensure kubebuilder binary is executable - echo "${kb_root_dir}/bin" >> $GITHUB_PATH # Add to PATH + go-version-file: go.mod - - name: Create Directory, Run Kubebuilder Commands, and Validate Results - env: - KUBEBUILDER_ASSETS: $GITHUB_WORKSPACE/bin - run: | - # Create a directory named testplugin for running kubebuilder commands - mkdir testplugin - cd testplugin - - # Run Kubebuilder commands inside the testplugin directory - kubebuilder init --plugins sampleexternalplugin/v1 --domain sample.domain.com - kubebuilder create api --plugins sampleexternalplugin/v1 --number=2 --group=example --version=v1alpha1 --kind=ExampleKind - kubebuilder create webhook --plugins sampleexternalplugin/v1 --hooked --group=example --version=v1alpha1 --kind=ExampleKind + - name: Run tests + run: make test-external-plugin - # Validate generated file contents - grep "DOMAIN: sample.domain.com" ./initFile.txt || exit 1 - grep "NUMBER: 2" ./apiFile.txt || exit 1 - grep "HOOKED!" ./webhookFile.txt || exit 1 diff --git a/.github/workflows/lint-sample.yml b/.github/workflows/lint-sample.yml index 2f7f38ea588..c764f6f153f 100644 --- a/.github/workflows/lint-sample.yml +++ b/.github/workflows/lint-sample.yml @@ -12,6 +12,7 @@ jobs: lint-samples: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: folder: [ "testdata/project-v4", @@ -36,11 +37,10 @@ jobs: working-directory: ${{ matrix.folder }} run: make lint-config - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.63.4 + version: v2.1.6 working-directory: ${{ matrix.folder }} - args: --config .golangci.yml ./... - name: Run linter via makefile target working-directory: ${{ matrix.folder }} run: make lint diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f59907535c7..db365c8fc76 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,9 +24,9 @@ jobs: - name: Check linter configuration run: make lint-config - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.63.4 + version: v2.1.6 yamllint: runs-on: ubuntu-latest diff --git a/.github/workflows/test-alpha-generate.yml b/.github/workflows/test-alpha-generate.yml index b0dc7d49b60..28986db95fd 100644 --- a/.github/workflows/test-alpha-generate.yml +++ b/.github/workflows/test-alpha-generate.yml @@ -35,6 +35,19 @@ jobs: run: | sed -i 's#go.kubebuilder.io/v4#go.kubebuilder.io/v3#g' testdata/project-v4/PROJECT + # Validate if help output is working and workaround to + # update the PROJECT file in memory to allow upgrade + # no longer supported layouts did not break the command options + - name: Validate help output for alpha generate + run: | + if kubebuilder alpha generate --help | grep -q "kubebuilder alpha generate \[flags\]"; then + echo "Help output validated" + else + echo "Help output missing or invalid" + exit 1 + fi + - name: Run kubebuilder alpha generate run: | - cd testdata/project-v4 && kubebuilder alpha generate \ No newline at end of file + cd testdata/project-v4 && kubebuilder alpha generate + diff --git a/.github/workflows/test-e2e-samples.yml b/.github/workflows/test-e2e-samples.yml index 97be2426b9f..b987c17eedd 100644 --- a/.github/workflows/test-e2e-samples.yml +++ b/.github/workflows/test-e2e-samples.yml @@ -39,12 +39,9 @@ jobs: - name: Prepare project-v4 run: | + # Enable [METRICS-WITH-CERTS] by uncommenting the lines in kustomization.yaml KUSTOMIZATION_FILE_PATH="testdata/project-v4/config/default/kustomization.yaml" - sed -i '25s/^#//' $KUSTOMIZATION_FILE_PATH sed -i '47,49s/^#//' $KUSTOMIZATION_FILE_PATH - # Uncomment all cert-manager injections - sed -i '59,234s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '236,251s/^#//' $KUSTOMIZATION_FILE_PATH cd testdata/project-v4/ go mod tidy @@ -81,17 +78,6 @@ jobs: - name: Prepare project-v4-with-plugins run: | - KUSTOMIZATION_FILE_PATH="testdata/project-v4-with-plugins/config/default/kustomization.yaml" - sed -i '25s/^#//' $KUSTOMIZATION_FILE_PATH - # Uncomment only ValidatingWebhookConfiguration - # from cert-manager replaces; we are leaving defaulting uncommented - # since this sample has no defaulting webhooks - sed -i '59,77s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '90,107s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '120,186s/^#//' $KUSTOMIZATION_FILE_PATH - # Uncomment only --conversion webhooks CA injection - sed -i '219,234s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '236,251s/^#//' $KUSTOMIZATION_FILE_PATH cd testdata/project-v4-with-plugins/ go mod tidy @@ -128,13 +114,6 @@ jobs: - name: Prepare project-v4-multigroup run: | - KUSTOMIZATION_FILE_PATH="testdata/project-v4-multigroup/config/default/kustomization.yaml" - sed -i '25s/^#//' $KUSTOMIZATION_FILE_PATH - # Uncomment all cert-manager injections for webhooks only - sed -i '59,77s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '90,107s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '120,234s/^#//' $KUSTOMIZATION_FILE_PATH - sed -i '236,251s/^#//' $KUSTOMIZATION_FILE_PATH cd testdata/project-v4-multigroup go mod tidy diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 2fe126601da..d4b5c285e13 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -13,14 +13,14 @@ jobs: uses: actions/checkout@v4 - name: Validate PR Title Format + env: + TITLE: ${{ github.event.pull_request.title }} run: | - TITLE="${{ github.event.pull_request.title }}" - if [[ -z "$TITLE" ]]; then echo "Error: PR title cannot be empty." exit 1 fi - + if ! [[ "$TITLE" =~ ^($'\u26A0'|$'\u2728'|$'\U0001F41B'|$'\U0001F4D6'|$'\U0001F680'|$'\U0001F331') ]]; then echo "Error: Invalid PR title format." echo "Your PR title must start with one of the following indicators:" @@ -32,5 +32,5 @@ jobs: echo "- Infra/Tests/Other: 🌱 (U+1F331)" exit 1 fi - + echo "PR title is valid: '$TITLE'" diff --git a/.golangci.yml b/.golangci.yml index 1ec4edba185..00ef274e974 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,92 +1,101 @@ +version: "2" run: - timeout: 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: - - linters: [gosec] - path: "test/e2e/*" - - path: "hack/docs/*" - linters: - - lll - - gosec - -linters-settings: - govet: - enable-all: true - disable: - - fieldalignment - - shadow - nolintlint: - allow-unused: false - revive: - rules: - # The following rules are recommended https://github.com/mgechev/revive#recommended-configuration - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - arguments: - # dot import should be ONLY allowed for ginkgo testing packages - - allowedPackages: - - "github.com/onsi/ginkgo/v2" - - "github.com/onsi/gomega" - - name: error-return - - name: error-strings - - name: error-naming - - name: exported - - name: if-return - - name: increment-decrement - - name: var-naming - - name: var-declaration - - name: package-comments - disabled: true # TODO: Investigate if it should be enabled. Disabled for now due to many findings. - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: empty-block - - name: superfluous-else - - name: unused-parameter - - name: unreachable-code - - name: redefines-builtin-id - # - # Rules in addition to the recommended configuration above. - # - - name: bool-literal-in-expr - - name: constant-logical-expr - - name: comment-spacings - linters: - disable-all: true + default: none enable: + - asciicheck + - bidichk + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll - misspell - - nolintlint - nakedret + - nolintlint - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - + - wrapcheck + - whitespace + settings: + ginkgolinter: + forbid-focus-container: true + forbid-spec-pollution: true + govet: + disable: + - fieldalignment + enable-all: true + nolintlint: + allow-unused: false + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + arguments: + - allowedPackages: + - github.com/onsi/ginkgo/v2 + - github.com/onsi/gomega + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: import-shadowing + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + disabled: true + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + - name: bool-literal-in-expr + - name: constant-logical-expr + - name: comment-spacings + exclusions: + generated: lax + rules: + - linters: + - gosec + path: test/e2e/* + - linters: + - gosec + - lll + path: hack/docs/* + - linters: + - revive + text: 'should have comment or be unexported' + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d94eaced5ed..f7fc4651b05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,9 +80,8 @@ To manually setup run: ```shell # To generate an Kubebuilder local binary with your changes make install -# To create the cluster and configure a CNI which supports NetworkPolicy +# To create the cluster kind create cluster --config ./test/e2e/kind-config.yaml -kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml ``` Now, you can for example, run in debug mode the `test/e2e/v4/e2e_suite_test.go`: diff --git a/Makefile b/Makefile index b539832be70..0bed2c6b59e 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,11 @@ help: ## Display this help ##@ Build +K8S_VERSION ?= $(shell go list -m -modfile=./testdata/project-v4/go.mod -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d.%d", $$3, $$4}') + LD_FLAGS=-ldflags " \ -X sigs.k8s.io/kubebuilder/v4/cmd.kubeBuilderVersion=$(shell git describe --tags --dirty --broken) \ + -X sigs.k8s.io/kubebuilder/v4/cmd.kubernetesVendorVersion=$(K8S_VERSION) \ -X sigs.k8s.io/kubebuilder/v4/cmd.goos=$(shell go env GOOS) \ -X sigs.k8s.io/kubebuilder/v4/cmd.goarch=$(shell go env GOARCH) \ -X sigs.k8s.io/kubebuilder/v4/cmd.gitCommit=$(shell git rev-parse HEAD) \ @@ -72,7 +75,7 @@ generate: generate-testdata generate-docs update-k8s-version ## Update/generate remove-spaces: @echo "Removing trailing spaces" @bash -c ' \ - if [[ "$$(uname)" == "Linux" ]]; then \ + if sed --version 2>&1 | grep -q "GNU"; then \ find . -type f -name "*.md" -exec sed -i "s/[[:space:]]*$$//" {} + || true; \ else \ find . -type f -name "*.md" -exec sed -i "" "s/[[:space:]]*$$//" {} + || true; \ @@ -124,8 +127,7 @@ yamllint: GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint golangci-lint: @[ -f $(GOLANGCI_LINT) ] || { \ - set -e ;\ - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) v1.63.4 ;\ + GOBIN=$(shell pwd)/bin go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 ;\ } .PHONY: apidiff @@ -154,6 +156,7 @@ test-coverage: ## Run unit tests creating the output to report coverage .PHONY: test-integration test-integration: ## Run the integration tests ./test/integration.sh + ./test/features.sh .PHONY: check-testdata check-testdata: ## Run the script to ensure that the testdata is updated @@ -182,6 +185,11 @@ test-book: ## Run the cronjob tutorial's unit tests to make sure we don't break test-license: ## Run the license check ./test/check-license.sh +.PHONY: test-external-plugin +test-external-plugin: install ## Run tests for external plugin + make -C docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1 install + make -C docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1 test-plugin + .PHONY: test-spaces test-spaces: ## Run the trailing spaces check ./test/check_spaces.sh @@ -201,7 +209,6 @@ install-helm: ## Install the latest version of Helm locally helm-lint: install-helm ## Lint the Helm chart in testdata helm lint testdata/project-v4-with-plugins/dist/chart -K8S_VERSION ?= $(shell go list -m -modfile=./testdata/project-v4/go.mod -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d.%d", $$3, $$4}') .PHONY: update-k8s-version update-k8s-version: ## Update Kubernetes API version in version.go and .goreleaser.yml @if [ -z "$(K8S_VERSION)" ]; then echo "Error: K8S_VERSION is empty"; exit 1; fi diff --git a/build/.goreleaser.yml b/build/.goreleaser.yml index 821800532b6..54827b78f84 100644 --- a/build/.goreleaser.yml +++ b/build/.goreleaser.yml @@ -46,7 +46,7 @@ builds: - darwin_amd64 - darwin_arm64 env: - - KUBERNETES_VERSION=1.32.1 + - KUBERNETES_VERSION=1.33.0 - CGO_ENABLED=0 # Only binaries of the form "kubebuilder_${goos}_${goarch}" will be released. diff --git a/cmd/cmd.go b/cmd/cmd.go index 2e6797c11a7..276c98791d4 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -55,6 +55,7 @@ func Run() { c, err := cli.New( cli.WithCommandName("kubebuilder"), cli.WithVersion(versionString()), + cli.WithCliVersion(getKubebuilderVersion()), cli.WithPlugins( golangv4.Plugin{}, gov4Bundle, diff --git a/cmd/version.go b/cmd/version.go index 77520ede984..a84c3ea576b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -27,7 +27,7 @@ const unknown = "unknown" // information in the release process var ( kubeBuilderVersion = unknown - kubernetesVendorVersion = "1.32.1" + kubernetesVendorVersion = "1.33.0" goos = unknown goarch = unknown gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) @@ -45,7 +45,7 @@ type version struct { GoArch string `json:"goArch"` } -// versionString returns the CLI version +// versionString returns the Full CLI version func versionString() string { if kubeBuilderVersion == unknown { if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" { @@ -62,3 +62,13 @@ func versionString() string { goarch, }) } + +// getKubebuilderVersion returns only the CLI version string +func getKubebuilderVersion() string { + if kubeBuilderVersion == unknown { + if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" { + kubeBuilderVersion = info.Main.Version + } + } + return kubeBuilderVersion +} diff --git a/designs/crd_version_conversion.md b/designs/crd_version_conversion.md index 72861318946..8176e024cde 100644 --- a/designs/crd_version_conversion.md +++ b/designs/crd_version_conversion.md @@ -158,12 +158,12 @@ func (ch *conversionHandler) convertObject(src, dst runtime.Object) error { err = src.(conversion.Convertible).ConvertTo(hub) if err != nil { - return fmt.Errorf("%T failed to convert to hub version %T : %v", src, hub, err) + return fmt.Errorf("%T failed to convert to hub version %T : %w", src, hub, err) } err = dst.(conversion.Convertible).ConvertFrom(hub) if err != nil { - return fmt.Errorf("%T failed to convert from hub version %T : %v", dst, hub, err) + return fmt.Errorf("%T failed to convert from hub version %T : %w", dst, hub, err) } return nil } diff --git a/designs/extensible-cli-and-scaffolding-plugins-phase-2.md b/designs/extensible-cli-and-scaffolding-plugins-phase-2.md index dfd17151311..02046857e9c 100644 --- a/designs/extensible-cli-and-scaffolding-plugins-phase-2.md +++ b/designs/extensible-cli-and-scaffolding-plugins-phase-2.md @@ -103,14 +103,14 @@ Note: If the name is ambiguous, then the qualified name `myexternalplugin.my.dom ### What Plugin system should we use -I propose we use our own plugin system that passes JSON blobs back and forth across `stdin/stdout/stderr` and make this the only option for now as it’s a language-agnostic medium and it is easy to work with in most languages. +I propose we use our own plugin system that passes JSON blobs back and forth across `stdin/stdout/stderr` and make this the only option for now as it's a language-agnostic medium and it is easy to work with in most languages. -We came to the conclusion that a kubebuilder-specific plugin library should be written after evaluating plugin libraries such as the [built-in go-plugin library](https://golang.org/pkg/plugin/) and [Hashicorp’s plugin library](https://github.com/hashicorp/go-plugin): +We came to the conclusion that a kubebuilder-specific plugin library should be written after evaluating plugin libraries such as the [built-in go-plugin library](https://golang.org/pkg/plugin/) and [Hashicorp's plugin library](https://github.com/hashicorp/go-plugin): -* The built-in plugin library seems to be more suitable for in-tree plugins rather than out-of-tree plugins and it doesn’t offer cross-language support, thereby making it a non-starter. -* Hashicorp’s go plugin system is more suitable than the built-in go-plugin library as it enables cross language/platform support. However, it is more suited for long running plugins as opposed to short lived plugins and the usage of protobuf could be overkill as we will not be handling 10s of 1000s of deserializations. +* The built-in plugin library seems to be more suitable for in-tree plugins rather than out-of-tree plugins and it doesn't offer cross-language support, thereby making it a non-starter. +* Hashicorp's go plugin system is more suitable than the built-in go-plugin library as it enables cross-language/platform support. However, it is more suited for long-running plugins as opposed to short-lived plugins and the usage of protobuf could be overkill as we will not be handling 10s of 1000s of deserializations. -In the future, if a need arises (for example, users are hitting performance issues), we can then explore the possibility of using the Hashicorp’s go plugin library. From a design standpoint, to leave it architecturally open, I propose using a `type` field in the PROJECT file to potentially allow other plugin libraries in the future and make this a seperate field in the PROJECT file per plugin; and this field determines how the `universe` will be passed for a given plugin. However, for the sake of simplicity in initial design and not to introduce any breaking changes as Project version 3 would suffice for our needs, this option is out of scope in this proposal. +In the future, if a need arises (for example, users are hitting performance issues), we can then explore the possibility of using the Hashicorp's go plugin library. From a design standpoint, to leave it architecturally open, I propose using a `type` field in the PROJECT file to potentially allow other plugin libraries in the future and make this a separate field in the PROJECT file per plugin; and this field determines how the `universe` will be passed for a given plugin. However, for the sake of simplicity in initial design and not to introduce any breaking changes as Project version 3 would suffice for our needs, this option is out of scope in this proposal. ### Project configuration @@ -165,14 +165,14 @@ resources: ![Kubebuilder to external plugins sequence diagram](https://github.com/rashmigottipati/POC-Phase2-Plugins/blob/main/docs/externalplugins-sequence-diagram.png) -* What to pass between `kubebuilder` and an external plugin? +* What should be passed between `kubebuilder` and an external plugin? Message passing between `kubebuilder` and the external plugin will occur through a request / response mechanism. The `PluginRequest` will contain information that `kubebuilder` sends *to* the external plugin. The `PluginResponse` will contain information that `kubebuilder` receives *from* the external plugin. -The following scenarios shows what `kubebuilder` will send/receive to the external plugin: +The following scenarios show what `kubebuilder` will send/receive to the external plugin: * `kubebuilder` to external plugin: - * `kubebuilder` constructs a `PluginRequest` that contains the `Command` (such as `init`, `create api`, or `create webhook`), `Args` containing all the raw flags from the CLI request and license boilerplate without comment delimiters, and an empty `Universe` that contains the current virtual state of file contents that is not written to the disk yet. `kubebuilder` writes the `PluginRequest` through `stdin`. + * `kubebuilder` constructs a `PluginRequest` that contains the `Command` (such as `init`, `create api`, or `create webhook`), `Args` containing all the raw flags from the CLI request and license boilerplate without comment delimiters, and an empty `Universe` that contains the current virtual state of file contents that are not written to disk yet. `kubebuilder` writes the `PluginRequest` through `stdin`. * External plugin to `kubebuilder`: * The plugin reads the `PluginRequest` through its `stdin` and processes the request based on the `Command` that was sent. If the `Command` doesn't match what the plugin supports, it writes back an error immediately without any further processing. If the `Command` matches what the plugin supports, it constructs a `PluginResponse` containing the `Command` that was executed by the plugin, and modified `Universe` based on the new files that were scaffolded by the external plugin, `Error` and `ErrorMsg` that add any error information, and writes the `PluginResponse` back to `kubebuilder` through `stdout`. @@ -314,7 +314,7 @@ What happens when the above is invoked? #### User specified file paths -A user will provide a list of file paths for `kubebuilder` to discover the plugins in. We will define a variable `KUBEBUILDER_PLUGINS_DIRS` that will take a list of file paths to search for the plugin name. It will also have a default value to search in, in case no file paths are provided. It will search for the plugin name that was provided to the `--plugins` flag in the CLI. `kubebuilder` will recursively search for all file paths until the plugin name is found and returns the successful match, and if it doesn’t exist, it returns an error message that the plugin is not found in the provided file paths. Also use the host system mechanism for PATH separation. +A user will provide a list of file paths for `kubebuilder` to discover the plugins in. We will define a variable `KUBEBUILDER_PLUGINS_DIRS` that will take a list of file paths to search for the plugin name. It will also have a default value to search in, in case no file paths are provided. It will search for the plugin name that was provided to the `--plugins` flag in the CLI. `kubebuilder` will recursively search for all file paths until the plugin name is found and returns the successful match, and if it doesn't exist, it returns an error message that the plugin is not found in the provided file paths. Also use the host system mechanism for PATH separation. * Alternatively, this could be handled in a way that [helm kustomize plugin](https://helm.sh/docs/topics/advanced/#post-rendering) discovers the plugin based on the non-existence of a separator in the path provided, in which case `kubebuilder` will search in `$PATH`, otherwise resolve any relative paths to a fully qualified path. @@ -330,9 +330,9 @@ A user will provide a list of file paths for `kubebuilder` to discover the plugi Another approach is adding plugin executables with a prefix `kubebuilder-` followed by the plugin name to the PATH variable. This will enable `kubebuilder` to traverse through the PATH looking for the plugin executables starting with the prefix `kubebuilder-` and matching by the plugin name that was provided in the CLI. Furthermore, a check should be added to verify that the match is an executable or not and return an error if it's not an executable. This approach provides a lot of flexibility in terms of plugin discovery as all the user needs to do is to add the plugin executable to the PATH and `kubebuilder` will discover it. * Pros - * `kubectl` and `git` follow the same approach for discovering plugins, so there’s prior art. + * `kubectl` and `git` follow the same approach for discovering plugins, so there's prior art. - * There’s a lot of flexibility in just dropping plugin binaries to PATH variable and enabling the discovery without having to enforce any other constraints on the placements of the plugins. + * There's a lot of flexibility in just dropping plugin binaries to PATH variable and enabling the discovery without having to enforce any other constraints on the placements of the plugins. * Cons * Enumerating the list of all available plugins might be a bit tough compared to having a single folder with the list of available plugins and having to enumerate those. diff --git a/designs/helper_to_upgrade_projects_by_rescaffolding.md b/designs/helper_to_upgrade_projects_by_rescaffolding.md index 7e49bf49d1f..344524ff5b1 100644 --- a/designs/helper_to_upgrade_projects_by_rescaffolding.md +++ b/designs/helper_to_upgrade_projects_by_rescaffolding.md @@ -29,7 +29,7 @@ the projects might have bug fixes and new incremental features added to the templates which will result in changes to the files that are generated by the tool for new projects. -In this case, you used previously the tool to generate the project +In this case, you previously used the tool to generate the project and now would like to update your project with the latest changes provided for the same plugin version. Therefore, you will need to: @@ -37,8 +37,8 @@ provided for the same plugin version. Therefore, you will need to: - You will run the command in the root directory of your project: `kubebuilder alpha generate` - Then, the command will remove the content of your local directory and re-scaffold the project from the scratch - It will allow you to compare your local branch with the remote branch of your project to re-add the code on top OR - if you do not use the flag `--no-backup` then you can compare the local directory with the copy of your project - copied to the path `.backup/project-name/` before the re-scaffold be done. + if you do not use the flag `--no-backup`, then you can compare the local directory with the copy of your project + copied to the path `.backup/project-name/` before the re-scaffold is done. - Therefore, you can run make all and test the final result. You will have after all your project updated. **To update the project with major changes provided** @@ -66,7 +66,7 @@ A common scenario is to upgrade the project based on the newer Kubebuilder. The The proposed command will automate the process at maximum, therefore helping operator authors with minimizing the manual effort. The main motivation of this proposal is to provide a helper for upgrades and -make less painful this process. Examples: +make this process less painful. Examples: - See the discussion [How to regenerate scaffolding?](https://github.com/kubernetes-sigs/kubebuilder/discussions/2864) - From [slack channel By Paul Laffitte](https://kubernetes.slack.com/archives/CAR30FCJZ/p1675166014762669) @@ -74,8 +74,8 @@ make less painful this process. Examples: ### Goals - Help users upgrade their project with the latest changes -- Help users to re-scaffold projects from scratch based on what was done previously with the tool -- Make less painful the process to upgrade +- Help users re-scaffold projects from scratch based on what was done previously with the tool +- Make the upgrade process less painful ### Non-Goals @@ -106,7 +106,7 @@ kubebuilder alpha generate \ - no-backup: [Optional] If not informed then, the current directory should be copied to the path `.backup/project-name` - backup: [Optional] If not informed then, the backup will be copied to the path `.backup/project-name` - plugins: [Optional] If not informed then, it is the same plugin chain available in the layout field -- binary: [Optional] If not informed then, the command will use KubeBuilder binary installed globaly. +- binary: [Optional] If not informed then, the command will use KubeBuilder binary installed globally. > Note that the backup created in the current directory must be prefixed with `.`. Otherwise the tool will not able to perform the scaffold to create a new project from the scratch. @@ -116,12 +116,12 @@ This command would mainly perform the following operations: - 1. Check the flags - 2. If the backup flag be used, then check if is a valid path and make a backup of the current project - 3. Copy the whole current directory to `.backup/project-name` -- 4. Ensure that the output path is clean. By default it is the current directory project where the project was scaffolded previously and it should be cleaned up before to do the re-scaffold. +- 4. Ensure that the output path is clean. By default it is the current directory project where the project was scaffolded previously and it should be cleaned up before doing the re-scaffold. Only the content under `.backup/project-name` should be kept. -- 4. Read the [PROJECT config][project-config] -- 5. Re-run all commands using the KubeBuilder binary to recreate the project in the output directory +- 5. Read the [PROJECT config][project-config] +- 6. Re-run all commands using the KubeBuilder binary to recreate the project in the output directory -The command should also provide a comprensive help with examples of the proposed workflows. So that, users +The command should also provide comprehensive help with examples of the proposed workflows. So that, users are able to understand how to use it when run `--help`. ### User Stories diff --git a/designs/integrating-kubebuilder-and-osdk.md b/designs/integrating-kubebuilder-and-osdk.md index df4ec362cc8..730e948cd66 100644 --- a/designs/integrating-kubebuilder-and-osdk.md +++ b/designs/integrating-kubebuilder-and-osdk.md @@ -2,12 +2,11 @@ |---------------|---------------|-------------|-------| | @joelanford | Sep 6, 2019 | implemented | - | -Integrating Kubebuilder and Operator SDK -======================================== +# Integrating Kubebuilder and Operator SDK ## Goal -To unite Kubebuilder and Operator SDK around Kubebuilder’s project scaffolding, to move Operator SDK’s Go operator features upstream, where appropriate, and to join forces on maintaining Kubebuilder so that both Kubebuilder and Operator SDK support the same project structure and command line interface for Go-based operators. +To unite Kubebuilder and Operator SDK around Kubebuilder's project scaffolding, to move Operator SDK's Go operator features upstream, where appropriate, and to join forces on maintaining Kubebuilder so that both Kubebuilder and Operator SDK support the same project structure and command line interface for Go-based operators. ## Background @@ -26,9 +25,9 @@ The Kubebuilder and Operator SDK contributors created a [GitHub project][kb-osdk ### Upstream code from Operator SDK The Operator SDK project contains various features that can be used by Go operator developers regardless of whether the project is based on Kubebuilder or Operator SDK. These features will be upstreamed into `kubebuilder`, `controller-runtime`, and `controller-tools`, where appropriate. These include: -* a `DynamicRESTMapper` that enables an operator to dynamically and automatically discover new CRDs added to the cluster after the operator has started -* a `GenerationChangedPredicate` that can trigger reconciliation events when a resource's `metadata.generation` field has changed. -* flags and helpers that can be used to provide more fine-grained configuration when constructing the default `zap`-based logger. +* A `DynamicRESTMapper` that enables an operator to dynamically and automatically discover new CRDs added to the cluster after the operator has started +* A `GenerationChangedPredicate` that can trigger reconciliation events when a resource's `metadata.generation` field has changed +* Flags and helpers that can be used to provide more fine-grained configuration when constructing the default `zap`-based logger The Operator SDK contributors plan to begin conducting all development of Go operator related code in upstream Kubebuilder (and related projects) and to spend more time helping the Kubebuilder contributors maintain these projects. @@ -36,7 +35,7 @@ The Operator SDK contributors plan to begin conducting all development of Go ope To make Kubebuilder more extensible, the community has been discussing a proposal to add extension points to Kubebuilder to support different operator patterns. One example of an operator pattern is the [addon pattern][addon-pattern-pr] that uses an existing library to instantiate an opinionated API and controller. -More broadly, the idea is to add support for executable plugin-based extensions that can modify Kubebuilder’s base scaffolding before files are written to disk so that the project (e.g. Go code, kustomize templates, the project Makefile and Dockerfile) can have customized content provided by an extension. +More broadly, the idea is to add support for executable plugin-based extensions that can modify Kubebuilder's base scaffolding before files are written to disk so that the project (e.g. Go code, kustomize templates, the project Makefile and Dockerfile) can have customized content provided by an extension. ### Documentation diff --git a/designs/simplified-scaffolding.md b/designs/simplified-scaffolding.md index bdbacaa8d8f..1d3bd07e53a 100644 --- a/designs/simplified-scaffolding.md +++ b/designs/simplified-scaffolding.md @@ -137,8 +137,8 @@ they think it is, we're probably better served coming up with a standard "can I create this example YAML file". Furthermore, since the structure is quite convoluted, it makes it more -difficult to write examples, since the actual code we care about ends up -scattered deep in a folder structure. +difficult to write examples, as the actual code we care about ends up +scattered deep in the folder structure. ### Lack of Builder diff --git a/designs/template.md b/designs/template.md index 36c13bca067..ffb9858efc8 100644 --- a/designs/template.md +++ b/designs/template.md @@ -2,8 +2,7 @@ |---------------|---------------|-------------|---| | @name | date | Implementable | - | -Title of the Design/Proposal -=================== +# Title of the Design/Proposal sample, externalplugin.py --> externalplugin trimmedPluginName := strings.Split(pluginFile.Name(), ".") if trimmedPluginName[0] == "" { - return nil, fmt.Errorf("Invalid plugin name found %q", pluginFile.Name()) + return nil, fmt.Errorf("invalid plugin name found %q", pluginFile.Name()) } if pluginFile.Name() == pluginInfo.Name() || trimmedPluginName[0] == pluginInfo.Name() { // check whether the external plugin is an executable. if !isPluginExecutable(pluginFile.Mode()) { - return nil, fmt.Errorf("External plugin %q found in path is not an executable", pluginFile.Name()) + return nil, fmt.Errorf("external plugin %q found in path is not an executable", pluginFile.Name()) } ep := external.Plugin{ @@ -310,18 +320,16 @@ func DiscoverExternalPlugins(filesystem afero.Fs) (ps []plugin.Plugin, err error Args: parseExternalPluginArgs(), } - if err := ep.PVersion.Parse(version.Name()); err != nil { - return nil, err + if err = ep.PVersion.Parse(version.Name()); err != nil { + return nil, fmt.Errorf("error parsing external plugin version %q: %w", version.Name(), err) } logrus.Printf("Adding external plugin: %s", ep.Name()) ps = append(ps, ep) - } } } - } return ps, nil diff --git a/pkg/cli/options_test.go b/pkg/cli/options_test.go index 3c406da8ca9..55d7bc3015f 100644 --- a/pkg/cli/options_test.go +++ b/pkg/cli/options_test.go @@ -38,8 +38,8 @@ import ( var _ = Describe("Discover external plugins", func() { Context("with valid plugins root path", func() { var ( - homePath string = os.Getenv("HOME") - customPath string = "/tmp/myplugins" + homePath string + customPath string // store user's original EXTERNAL_PLUGINS_PATH originalPluginPath string xdghome string @@ -47,6 +47,11 @@ var _ = Describe("Discover external plugins", func() { originalXdghome string ) + BeforeEach(func() { + homePath = os.Getenv("HOME") + customPath = "/tmp/myplugins" + }) + When("XDG_CONFIG_HOME is not set and using the $HOME environment variable", func() { // store and unset the XDG_CONFIG_HOME BeforeEach(func() { @@ -351,7 +356,7 @@ var _ = Describe("Discover external plugins", func() { plugins, err = DiscoverExternalPlugins(filesystem.FS) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Invalid plugin name found")) + Expect(err.Error()).To(ContainSubstring("invalid plugin name found")) Expect(plugins).To(BeEmpty()) }) }) @@ -395,7 +400,7 @@ var _ = Describe("Discover external plugins", func() { _, err = DiscoverExternalPlugins(filesystem.FS) Expect(err).To(HaveOccurred()) - Expect(err).To(Equal(errPluginsRoot)) + Expect(errors.Unwrap(err)).To(MatchError(errPluginsRoot)) }) It("should skip parsing of directories if plugins root is not a directory", func() { @@ -504,14 +509,24 @@ var _ = Describe("CLI options", func() { c *CLI err error + projectVersion config.Version + + p plugin.Plugin + np1 plugin.Plugin + np2 mockPlugin + np3 plugin.Plugin + np4 plugin.Plugin + ) + + BeforeEach(func() { projectVersion = config.Version{Number: 1} - p = newMockPlugin(pluginName, pluginVersion, projectVersion) + p = newMockPlugin(pluginName, pluginVersion, projectVersion) np1 = newMockPlugin("Plugin", pluginVersion, projectVersion) np2 = mockPlugin{pluginName, plugin.Version{Number: -1}, []config.Version{projectVersion}} np3 = newMockPlugin(pluginName, pluginVersion) np4 = newMockPlugin(pluginName, pluginVersion, config.Version{}) - ) + }) Context("WithCommandName", func() { It("should use provided command name", func() { diff --git a/pkg/cli/resource_test.go b/pkg/cli/resource_test.go index f1d646ebded..547beaebc60 100644 --- a/pkg/cli/resource_test.go +++ b/pkg/cli/resource_test.go @@ -32,6 +32,12 @@ var _ = Describe("resourceOptions", func() { ) var ( + fullGVK resource.GVK + noDomainGVK resource.GVK + noGroupGVK resource.GVK + ) + + BeforeEach(func() { fullGVK = resource.GVK{ Group: group, Domain: domain, @@ -48,7 +54,7 @@ var _ = Describe("resourceOptions", func() { Version: version, Kind: kind, } - ) + }) Context("validate", func() { DescribeTable("should succeed for valid options", @@ -68,13 +74,14 @@ var _ = Describe("resourceOptions", func() { Context("newResource", func() { DescribeTable("should succeed if the Resource is valid", - func(options resourceOptions) { + func(getOpts func() resourceOptions) { + options := getOpts() + Expect(options.validate()).To(Succeed()) resource := options.newResource() Expect(resource.Validate()).To(Succeed()) Expect(resource.GVK.IsEqualTo(options.GVK)).To(BeTrue()) - // Plural is checked in the next test Expect(resource.Path).To(Equal("")) Expect(resource.API).NotTo(BeNil()) Expect(resource.API.CRDVersion).To(Equal("")) @@ -86,9 +93,9 @@ var _ = Describe("resourceOptions", func() { Expect(resource.Webhooks.Validation).To(BeFalse()) Expect(resource.Webhooks.Conversion).To(BeFalse()) }, - Entry("full GVK", resourceOptions{GVK: fullGVK}), - Entry("missing domain", resourceOptions{GVK: noDomainGVK}), - Entry("missing group", resourceOptions{GVK: noGroupGVK}), + Entry("full GVK", func() resourceOptions { return resourceOptions{GVK: fullGVK} }), + Entry("missing domain", func() resourceOptions { return resourceOptions{GVK: noDomainGVK} }), + Entry("missing group", func() resourceOptions { return resourceOptions{GVK: noGroupGVK} }), ) DescribeTable("should default the Plural by pluralizing the Kind", diff --git a/pkg/cli/suite_test.go b/pkg/cli/suite_test.go index b25970be22c..50108497183 100644 --- a/pkg/cli/suite_test.go +++ b/pkg/cli/suite_test.go @@ -37,7 +37,7 @@ var ( _ plugin.Plugin = mockDeprecatedPlugin{} ) -type mockPlugin struct { //nolint:maligned +type mockPlugin struct { name string version plugin.Version projectVersions []config.Version @@ -55,7 +55,7 @@ func (p mockPlugin) Name() string { return p.name func (p mockPlugin) Version() plugin.Version { return p.version } func (p mockPlugin) SupportedProjectVersions() []config.Version { return p.projectVersions } -type mockDeprecatedPlugin struct { //nolint:maligned +type mockDeprecatedPlugin struct { mockPlugin deprecation string } diff --git a/pkg/config/errors_test.go b/pkg/config/errors_test.go index 5ec02ad2576..98858af8052 100644 --- a/pkg/config/errors_test.go +++ b/pkg/config/errors_test.go @@ -26,9 +26,13 @@ import ( ) var _ = Describe("UnsupportedVersionError", func() { - err := UnsupportedVersionError{ - Version: Version{Number: 1}, - } + var err UnsupportedVersionError + + BeforeEach(func() { + err = UnsupportedVersionError{ + Version: Version{Number: 1}, + } + }) Context("Error", func() { It("should return the correct error message", func() { @@ -38,10 +42,14 @@ var _ = Describe("UnsupportedVersionError", func() { }) var _ = Describe("UnsupportedFieldError", func() { - err := UnsupportedFieldError{ - Version: Version{Number: 1}, - Field: "name", - } + var err UnsupportedFieldError + + BeforeEach(func() { + err = UnsupportedFieldError{ + Version: Version{Number: 1}, + Field: "name", + } + }) Context("Error", func() { It("should return the correct error message", func() { @@ -51,14 +59,18 @@ var _ = Describe("UnsupportedFieldError", func() { }) var _ = Describe("ResourceNotFoundError", func() { - err := ResourceNotFoundError{ - GVK: resource.GVK{ - Group: "group", - Domain: "my.domain", - Version: "v1", - Kind: "Kind", - }, - } + var err ResourceNotFoundError + + BeforeEach(func() { + err = ResourceNotFoundError{ + GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }, + } + }) Context("Error", func() { It("should return the correct error message", func() { @@ -68,9 +80,13 @@ var _ = Describe("ResourceNotFoundError", func() { }) var _ = Describe("PluginKeyNotFoundError", func() { - err := PluginKeyNotFoundError{ - Key: "go.kubebuilder.io/v1", - } + var err PluginKeyNotFoundError + + BeforeEach(func() { + err = PluginKeyNotFoundError{ + Key: "go.kubebuilder.io/v1", + } + }) Context("Error", func() { It("should return the correct error message", func() { @@ -81,10 +97,15 @@ var _ = Describe("PluginKeyNotFoundError", func() { var _ = Describe("MarshalError", func() { var ( - wrapped = fmt.Errorf("error message") - err = MarshalError{Err: wrapped} + wrapped error + err MarshalError ) + BeforeEach(func() { + wrapped = fmt.Errorf("wrapped error") + err = MarshalError{Err: wrapped} + }) + Context("Error", func() { It("should return the correct error message", func() { Expect(err.Error()).To(Equal(fmt.Sprintf("error marshalling project configuration: %v", wrapped))) @@ -100,10 +121,15 @@ var _ = Describe("MarshalError", func() { var _ = Describe("UnmarshalError", func() { var ( - wrapped = fmt.Errorf("error message") - err = UnmarshalError{Err: wrapped} + wrapped error + err UnmarshalError ) + BeforeEach(func() { + wrapped = fmt.Errorf("wrapped error") + err = UnmarshalError{Err: wrapped} + }) + Context("Error", func() { It("should return the correct error message", func() { Expect(err.Error()).To(Equal(fmt.Sprintf("error unmarshalling project configuration: %v", wrapped))) diff --git a/pkg/config/interface.go b/pkg/config/interface.go index 1eb9ac949a9..b97a52417a4 100644 --- a/pkg/config/interface.go +++ b/pkg/config/interface.go @@ -27,6 +27,12 @@ type Config interface { // GetVersion returns the current project version. GetVersion() Version + // GetCliVersion returns the CLI binary version that was used to scaffold or initialize the project. + GetCliVersion() string + + // SetCliVersion sets the binary version used to initialize the project. + SetCliVersion(version string) error + /* String fields */ // GetDomain returns the project domain. diff --git a/pkg/config/registry_test.go b/pkg/config/registry_test.go index 11e9ea408cc..f552665a30a 100644 --- a/pkg/config/registry_test.go +++ b/pkg/config/registry_test.go @@ -23,10 +23,15 @@ import ( var _ = Describe("registry", func() { var ( - version = Version{} - f = func() Config { return nil } + version Version + f func() Config ) + BeforeEach(func() { + version = Version{} + f = func() Config { return nil } + }) + AfterEach(func() { registry = make(map[Version]func() Config) }) diff --git a/pkg/config/store/errors_test.go b/pkg/config/store/errors_test.go index ef62a6efe93..a7da63b306e 100644 --- a/pkg/config/store/errors_test.go +++ b/pkg/config/store/errors_test.go @@ -31,10 +31,15 @@ func TestConfigStore(t *testing.T) { var _ = Describe("LoadError", func() { var ( - wrapped = fmt.Errorf("error message") - err = LoadError{Err: wrapped} + wrapped error + err LoadError ) + BeforeEach(func() { + wrapped = fmt.Errorf("error message") + err = LoadError{Err: wrapped} + }) + Context("Error", func() { It("should return the correct error message", func() { Expect(err.Error()).To(Equal(fmt.Sprintf("unable to load the configuration: %v", wrapped))) @@ -50,10 +55,15 @@ var _ = Describe("LoadError", func() { var _ = Describe("SaveError", func() { var ( - wrapped = fmt.Errorf("error message") - err = SaveError{Err: wrapped} + wrapped error + err SaveError ) + BeforeEach(func() { + wrapped = fmt.Errorf("error message") + err = SaveError{Err: wrapped} + }) + Context("Error", func() { It("should return the correct error message", func() { Expect(err.Error()).To(Equal(fmt.Sprintf("unable to save the configuration: %v", wrapped))) diff --git a/pkg/config/store/yaml/store.go b/pkg/config/store/yaml/store.go index 7c82446a13e..3899c67d60e 100644 --- a/pkg/config/store/yaml/store.go +++ b/pkg/config/store/yaml/store.go @@ -60,7 +60,7 @@ func New(fs machinery.Filesystem) store.Store { func (s *yamlStore) New(version config.Version) error { cfg, err := config.New(version) if err != nil { - return err + return fmt.Errorf("could not create config: %w", err) } s.cfg = cfg diff --git a/pkg/config/store/yaml/store_test.go b/pkg/config/store/yaml/store_test.go index b939e178272..3e186978fd0 100644 --- a/pkg/config/store/yaml/store_test.go +++ b/pkg/config/store/yaml/store_test.go @@ -18,6 +18,7 @@ package yaml import ( "errors" + "fmt" "os" "testing" @@ -62,13 +63,13 @@ layout: "" ) var ( - s *yamlStore - - path = DefaultPath + "2" + s *yamlStore + path string ) BeforeEach(func() { s = New(machinery.Filesystem{FS: afero.NewMemMapFs()}).(*yamlStore) + path = DefaultPath + "2" }) Context("New", func() { @@ -91,7 +92,13 @@ layout: "" It("should fail if no file exists at the default path", func() { err := s.Load() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to read %q file: %w", DefaultPath, &os.PathError{ + Err: os.ErrNotExist, + Path: DefaultPath, + Op: "open", + }), + })) }) It("should fail if unable to identify the version of the file at the default path", func() { @@ -99,7 +106,15 @@ layout: "" err := s.Load() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to determine config version: %w", + fmt.Errorf("error unmarshaling JSON: %w", + fmt.Errorf("while decoding JSON: %w", + errors.New("project version is empty"), + ), + ), + ), + })) }) It("should fail if unable to create a Config for the version of the file at the default path", func() { @@ -107,7 +122,11 @@ layout: "" err := s.Load() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to create config for version %q: %w", "1-alpha", config.UnsupportedVersionError{ + Version: config.Version{Number: 1, Stage: 2}, + }), + })) }) It("should fail if unable to unmarshal the file at the default path", func() { @@ -115,7 +134,14 @@ layout: "" err := s.Load() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to create config for version %q: %w", "2", config.UnsupportedVersionError{ + Version: config.Version{ + Number: 2, + Stage: 0, + }, + }), + })) }) }) @@ -133,7 +159,13 @@ layout: "" It("should fail if no file exists at the specified path", func() { err := s.LoadFrom(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to read %q file: %w", path, &os.PathError{ + Err: os.ErrNotExist, + Path: path, + Op: "open", + }), + })) }) It("should fail if unable to identify the version of the file at the specified path", func() { @@ -141,7 +173,15 @@ layout: "" err := s.LoadFrom(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to determine config version: %w", + fmt.Errorf("error unmarshaling JSON: %w", + fmt.Errorf("while decoding JSON: %w", + errors.New("project version is empty"), + ), + ), + ), + })) }) It("should fail if unable to create a Config for the version of the file at the specified path", func() { @@ -149,7 +189,11 @@ layout: "" err := s.LoadFrom(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to create config for version %q: %w", "1-alpha", config.UnsupportedVersionError{ + Version: config.Version{Number: 1, Stage: 2}, + }), + })) }) It("should fail if unable to unmarshal the file at the specified path", func() { @@ -157,7 +201,13 @@ layout: "" err := s.LoadFrom(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.LoadError{})).To(BeTrue()) + Expect(err).To(MatchError(store.LoadError{ + Err: fmt.Errorf("unable to create config for version %q: %w", "2", config.UnsupportedVersionError{ + Version: config.Version{ + Number: 2, + }, + }), + })) }) }) @@ -184,7 +234,9 @@ layout: "" It("should fail for an empty config", func() { err := s.Save() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + Expect(err).To(MatchError(store.SaveError{ + Err: errors.New("undefined config, use one of the initializers: New, Load, LoadFrom"), + })) }) It("should fail for a pre-existent file that must not exist", func() { @@ -194,7 +246,9 @@ layout: "" err := s.Save() Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + Expect(err).To(MatchError(store.SaveError{ + Err: fmt.Errorf("configuration already exists in %q", DefaultPath), + })) }) }) @@ -221,7 +275,9 @@ layout: "" It("should fail for an empty config", func() { err := s.SaveTo(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + Expect(err).To(MatchError(store.SaveError{ + Err: errors.New("undefined config, use one of the initializers: New, Load, LoadFrom"), + })) }) It("should fail for a pre-existent file that must not exist", func() { @@ -231,7 +287,9 @@ layout: "" err := s.SaveTo(path) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &store.SaveError{})).To(BeTrue()) + Expect(err).To(MatchError(store.SaveError{ + Err: fmt.Errorf("configuration already exists in %q", path), + })) }) }) }) diff --git a/pkg/config/v3/config.go b/pkg/config/v3/config.go index 9c7dfb38105..fac720601d5 100644 --- a/pkg/config/v3/config.go +++ b/pkg/config/v3/config.go @@ -38,7 +38,7 @@ func (ss *stringSlice) UnmarshalJSON(b []byte) error { if b[0] == '[' { var sl []string if err := yaml.Unmarshal(b, &sl); err != nil { - return err + return fmt.Errorf("error unmarshalling string slice %q: %w", sl, err) } *ss = sl return nil @@ -46,7 +46,7 @@ func (ss *stringSlice) UnmarshalJSON(b []byte) error { var st string if err := yaml.Unmarshal(b, &st); err != nil { - return err + return fmt.Errorf("error unmarshalling string %q: %w", st, err) } *ss = stringSlice{st} return nil @@ -61,6 +61,7 @@ type Cfg struct { Domain string `json:"domain,omitempty"` Repository string `json:"repo,omitempty"` Name string `json:"projectName,omitempty"` + CliVersion string `json:"cliVersion,omitempty"` PluginChain stringSlice `json:"layout,omitempty"` // Boolean fields @@ -93,6 +94,17 @@ func (c Cfg) GetVersion() config.Version { return c.Version } +// GetCliVersion implements config.Config +func (c Cfg) GetCliVersion() string { + return c.CliVersion +} + +// SetCliVersion implements config.Config +func (c *Cfg) SetCliVersion(version string) error { + c.CliVersion = version + return nil +} + // GetDomain implements config.Config func (c Cfg) GetDomain() string { return c.Domain @@ -234,8 +246,12 @@ func (c *Cfg) UpdateResource(res resource.Resource) error { } for i, r := range c.Resources { - if res.GVK.IsEqualTo(r.GVK) { - return c.Resources[i].Update(res) + if res.IsEqualTo(r.GVK) { + if err := c.Resources[i].Update(res); err != nil { + return fmt.Errorf("failed to update resource %q: %w", res.GVK, err) + } + + return nil } } @@ -318,11 +334,11 @@ func (c *Cfg) EncodePluginConfig(key string, configObj interface{}) error { // Get object's bytes and set them under key in extra fields. b, err := yaml.Marshal(configObj) if err != nil { - return fmt.Errorf("failed to convert %T object to bytes: %s", configObj, err) + return fmt.Errorf("failed to convert %T object to bytes: %w", configObj, err) } var fields map[string]interface{} if err := yaml.Unmarshal(b, &fields); err != nil { - return fmt.Errorf("failed to unmarshal %T object bytes: %s", configObj, err) + return fmt.Errorf("failed to unmarshal %T object bytes: %w", configObj, err) } if c.Plugins == nil { c.Plugins = make(map[string]pluginConfig) diff --git a/pkg/config/v3/config_test.go b/pkg/config/v3/config_test.go index 57a6d371e1a..fb954335663 100644 --- a/pkg/config/v3/config_test.go +++ b/pkg/config/v3/config_test.go @@ -17,7 +17,6 @@ limitations under the License. package v3 import ( - "errors" "sort" "testing" @@ -45,14 +44,15 @@ var _ = Describe("Cfg", func() { ) var ( - c Cfg + c Cfg + pluginChain []string + otherPluginChain []string + ) + BeforeEach(func() { pluginChain = []string{"go.kubebuilder.io/v2"} - otherPluginChain = []string{"go.kubebuilder.io/v3"} - ) - BeforeEach(func() { c = Cfg{ Version: Version, Domain: domain, @@ -136,6 +136,12 @@ var _ = Describe("Cfg", func() { Context("Resources", func() { var ( + res resource.Resource + resWithoutPlural resource.Resource + checkResource func(result, expected resource.Resource) + ) + + BeforeEach(func() { res = resource.Resource{ GVK: resource.GVK{ Group: "group", @@ -157,35 +163,35 @@ var _ = Describe("Cfg", func() { }, } resWithoutPlural = res.Copy() - ) - // As some of the tests insert directly into the slice without using the interface methods, - // regular plural forms should not be present in here. rsWithoutPlural is used for this purpose. - resWithoutPlural.Plural = "" - - // Auxiliary function for GetResource, AddResource and UpdateResource tests - checkResource := func(result, expected resource.Resource) { - Expect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue()) - Expect(result.Plural).To(Equal(expected.Plural)) - Expect(result.Path).To(Equal(expected.Path)) - if expected.API == nil { - Expect(result.API).To(BeNil()) - } else { - Expect(result.API).NotTo(BeNil()) - Expect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion)) - Expect(result.API.Namespaced).To(Equal(expected.API.Namespaced)) - } - Expect(result.Controller).To(Equal(expected.Controller)) - if expected.Webhooks == nil { - Expect(result.Webhooks).To(BeNil()) - } else { - Expect(result.Webhooks).NotTo(BeNil()) - Expect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion)) - Expect(result.Webhooks.Defaulting).To(Equal(expected.Webhooks.Defaulting)) - Expect(result.Webhooks.Validation).To(Equal(expected.Webhooks.Validation)) - Expect(result.Webhooks.Conversion).To(Equal(expected.Webhooks.Conversion)) + // As some of the tests insert directly into the slice without using the interface methods, + // regular plural forms should not be present in here. rsWithoutPlural is used for this purpose. + resWithoutPlural.Plural = "" + + // Auxiliary function for GetResource, AddResource and UpdateResource tests + checkResource = func(result, expected resource.Resource) { + Expect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue()) + Expect(result.Plural).To(Equal(expected.Plural)) + Expect(result.Path).To(Equal(expected.Path)) + if expected.API == nil { + Expect(result.API).To(BeNil()) + } else { + Expect(result.API).NotTo(BeNil()) + Expect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion)) + Expect(result.API.Namespaced).To(Equal(expected.API.Namespaced)) + } + Expect(result.Controller).To(Equal(expected.Controller)) + if expected.Webhooks == nil { + Expect(result.Webhooks).To(BeNil()) + } else { + Expect(result.Webhooks).NotTo(BeNil()) + Expect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion)) + Expect(result.Webhooks.Defaulting).To(Equal(expected.Webhooks.Defaulting)) + Expect(result.Webhooks.Validation).To(Equal(expected.Webhooks.Validation)) + Expect(result.Webhooks.Conversion).To(Equal(expected.Webhooks.Conversion)) + } } - } + }) DescribeTable("ResourcesLength should return the number of resources", func(n int) { @@ -354,6 +360,11 @@ var _ = Describe("Cfg", func() { ) var ( + c0, c1, c2 Cfg + pluginCfg PluginConfig + ) + + BeforeEach(func() { c0 = Cfg{ Version: Version, Domain: domain, @@ -386,45 +397,43 @@ var _ = Describe("Cfg", func() { }, }, } - pluginConfig = PluginConfig{ + pluginCfg = PluginConfig{ Data1: "plugin value 1", Data2: "plugin value 2", } - ) + }) It("DecodePluginConfig should fail for no plugin config object", func() { - var pluginCfg PluginConfig err := c0.DecodePluginConfig(key, &pluginCfg) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &config.PluginKeyNotFoundError{})).To(BeTrue()) + Expect(err).To(MatchError(config.PluginKeyNotFoundError{Key: key})) }) It("DecodePluginConfig should fail to retrieve data from a non-existent plugin", func() { - var pluginCfg PluginConfig err := c1.DecodePluginConfig("plugin-y", &pluginCfg) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &config.PluginKeyNotFoundError{})).To(BeTrue()) + Expect(err).To(MatchError(config.PluginKeyNotFoundError{Key: "plugin-y"})) }) DescribeTable("DecodePluginConfig should retrieve the plugin data correctly", - func(inputConfig Cfg, expectedPluginConfig PluginConfig) { - var pluginCfg PluginConfig - Expect(inputConfig.DecodePluginConfig(key, &pluginCfg)).To(Succeed()) - Expect(pluginCfg).To(Equal(expectedPluginConfig)) + func(getCfg func() Cfg, expected func() PluginConfig) { + pluginCfg = PluginConfig{} // reset to not reuse values + Expect(getCfg().DecodePluginConfig(key, &pluginCfg)).To(Succeed()) + Expect(pluginCfg).To(Equal(expected())) }, - Entry("for an empty plugin config object", c1, PluginConfig{}), - Entry("for a full plugin config object", c2, pluginConfig), + Entry("for an empty plugin config object", func() Cfg { return c1 }, func() PluginConfig { return PluginConfig{} }), + Entry("for a full plugin config object", func() Cfg { return c2 }, func() PluginConfig { return pluginCfg }), // TODO (coverage): add cases where yaml.Marshal returns an error // TODO (coverage): add cases where yaml.Unmarshal returns an error ) DescribeTable("EncodePluginConfig should encode the plugin data correctly", - func(pluginConfig PluginConfig, expectedConfig Cfg) { - Expect(c.EncodePluginConfig(key, pluginConfig)).To(Succeed()) - Expect(c).To(Equal(expectedConfig)) + func(getPluginCfg func() PluginConfig, expectedCfg func() Cfg) { + Expect(c.EncodePluginConfig(key, getPluginCfg())).To(Succeed()) + Expect(c).To(Equal(expectedCfg())) }, - Entry("for an empty plugin config object", PluginConfig{}, c1), - Entry("for a full plugin config object", pluginConfig, c2), + Entry("for an empty plugin config object", func() PluginConfig { return PluginConfig{} }, func() Cfg { return c1 }), + Entry("for a full plugin config object", func() PluginConfig { return pluginCfg }, func() Cfg { return c2 }), // TODO (coverage): add cases where yaml.Marshal returns an error // TODO (coverage): add cases where yaml.Unmarshal returns an error ) @@ -432,7 +441,11 @@ var _ = Describe("Cfg", func() { Context("Persistence", func() { var ( - // BeforeEach is called after the entries are evaluated, and therefore, c is not available + c1, c2 Cfg + s1, s1bis, s2 string + ) + + BeforeEach(func() { c1 = Cfg{ Version: Version, Domain: domain, @@ -472,8 +485,8 @@ var _ = Describe("Cfg", func() { Kind: "Kind", }, Plural: "kindes", - API: &resource.API{}, - Webhooks: &resource.Webhooks{}, + API: nil, + Webhooks: nil, }, { GVK: resource.GVK{ @@ -564,22 +577,23 @@ resources: webhookVersion: v1 version: "3" ` - ) + }) DescribeTable("MarshalYAML should succeed", - func(c Cfg, content string) { - b, err := c.MarshalYAML() + func(getCfg func() Cfg, getContent func() string) { + b, err := getCfg().MarshalYAML() Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(Equal(content)) + Expect(string(b)).To(Equal(getContent())) }, - Entry("for a basic configuration", c1, s1), - Entry("for a full configuration", c2, s2), + Entry("for a basic configuration", func() Cfg { return c1 }, func() string { return s1 }), + Entry("for a full configuration", func() Cfg { return c2 }, func() string { return s2 }), ) DescribeTable("UnmarshalYAML should succeed", - func(content string, c Cfg) { + func(getContent func() string, getCfg func() Cfg) { var unmarshalled Cfg - Expect(unmarshalled.UnmarshalYAML([]byte(content))).To(Succeed()) + Expect(unmarshalled.UnmarshalYAML([]byte(getContent()))).To(Succeed()) + c := getCfg() Expect(unmarshalled.Version.Compare(c.Version)).To(Equal(0)) Expect(unmarshalled.Domain).To(Equal(c.Domain)) Expect(unmarshalled.Repository).To(Equal(c.Repository)) @@ -590,9 +604,9 @@ version: "3" Expect(unmarshalled.Plugins).To(HaveLen(len(c.Plugins))) // TODO: fully test Plugins field and not on its length }, - Entry("basic", s1, c1), - Entry("full", s2, c2), - Entry("string layout", s1bis, c1), + Entry("basic", func() string { return s1 }, func() Cfg { return c1 }), + Entry("full", func() string { return s2 }, func() Cfg { return c2 }), + Entry("string layout", func() string { return s1bis }, func() Cfg { return c1 }), ) DescribeTable("UnmarshalYAML should fail", diff --git a/pkg/config/version.go b/pkg/config/version.go index 0288e9aed75..2238af69e94 100644 --- a/pkg/config/version.go +++ b/pkg/config/version.go @@ -54,14 +54,14 @@ func (v *Version) Parse(version string) error { if n, errParse := strconv.Atoi(version); errParse == nil && n < 0 { return errNonPositive } - return err + return fmt.Errorf("failed to convert version number %q: %w", substrings[0], err) } else if v.Number == 0 { return errNonPositive } if len(substrings) > 1 { if err = v.Stage.Parse(substrings[1]); err != nil { - return err + return fmt.Errorf("failed to parse stage: %w", err) } } @@ -83,7 +83,11 @@ func (v Version) Validate() error { return errNonPositive } - return v.Stage.Validate() + if err := v.Stage.Validate(); err != nil { + return fmt.Errorf("failed to validate stage: %w", err) + } + + return nil } // Compare returns -1 if v < other, 0 if v == other, and 1 if v > other. @@ -105,17 +109,22 @@ func (v Version) IsStable() bool { // MarshalJSON implements json.Marshaller func (v Version) MarshalJSON() ([]byte, error) { if err := v.Validate(); err != nil { - return []byte{}, err + return []byte{}, fmt.Errorf("failed to validate version: %w", err) + } + + marshaled, err := json.Marshal(v.String()) + if err != nil { + return []byte{}, fmt.Errorf("failed to marshal version: %w", err) } - return json.Marshal(v.String()) + return marshaled, nil } // UnmarshalJSON implements json.Unmarshaller func (v *Version) UnmarshalJSON(b []byte) error { var str string if err := json.Unmarshal(b, &str); err != nil { - return err + return fmt.Errorf("failed to unmarshal version: %w", err) } return v.Parse(str) diff --git a/pkg/config/version_test.go b/pkg/config/version_test.go index 8b72f1bf1cf..d608a521f3b 100644 --- a/pkg/config/version_test.go +++ b/pkg/config/version_test.go @@ -29,8 +29,13 @@ var _ = Describe("Version", func() { // Parse, String and Validate are tested by MarshalJSON and UnmarshalJSON Context("Compare", func() { - // Test Compare() by sorting a list. var ( + versions []Version + sortedVersions []Version + ) + + BeforeEach(func() { + // Test Compare() by sorting a list. versions = []Version{ {Number: 2, Stage: stage.Alpha}, {Number: 44, Stage: stage.Alpha}, @@ -56,7 +61,7 @@ var _ = Describe("Version", func() { {Number: 44, Stage: stage.Alpha}, {Number: 44, Stage: stage.Alpha}, } - ) + }) It("sorts a valid list of versions correctly", func() { sort.Slice(versions, func(i int, j int) bool { diff --git a/pkg/machinery/errors_test.go b/pkg/machinery/errors_test.go index 67f1bc1ebc0..b0bbd1a754a 100644 --- a/pkg/machinery/errors_test.go +++ b/pkg/machinery/errors_test.go @@ -26,23 +26,30 @@ import ( var _ = Describe("Errors", func() { var ( - path = filepath.Join("path", "to", "file") - testErr = errors.New("test error") + path string + testErr error ) + BeforeEach(func() { + path = filepath.Join("path", "to", "file") + testErr = errors.New("test error") + }) + DescribeTable("should contain the wrapped error", - func(err error) { + func(getErr func() error) { + err := getErr() + Expect(err).To(HaveOccurred()) Expect(errors.Is(err, testErr)).To(BeTrue()) }, - Entry("for validate errors", ValidateError{testErr}), - Entry("for set template defaults errors", SetTemplateDefaultsError{testErr}), - Entry("for file existence errors", ExistsFileError{testErr}), - Entry("for file opening errors", OpenFileError{testErr}), - Entry("for directory creation errors", CreateDirectoryError{testErr}), - Entry("for file creation errors", CreateFileError{testErr}), - Entry("for file reading errors", ReadFileError{testErr}), - Entry("for file writing errors", WriteFileError{testErr}), - Entry("for file closing errors", CloseFileError{testErr}), + Entry("for validate errors", func() error { return ValidateError{testErr} }), + Entry("for set template defaults errors", func() error { return SetTemplateDefaultsError{testErr} }), + Entry("for file existence errors", func() error { return ExistsFileError{testErr} }), + Entry("for file opening errors", func() error { return OpenFileError{testErr} }), + Entry("for directory creation errors", func() error { return CreateDirectoryError{testErr} }), + Entry("for file creation errors", func() error { return CreateFileError{testErr} }), + Entry("for file reading errors", func() error { return ReadFileError{testErr} }), + Entry("for file writing errors", func() error { return WriteFileError{testErr} }), + Entry("for file closing errors", func() error { return CloseFileError{testErr} }), ) // NOTE: the following test increases coverage diff --git a/pkg/machinery/injector_test.go b/pkg/machinery/injector_test.go index 8296ceb7693..13a56739419 100644 --- a/pkg/machinery/injector_test.go +++ b/pkg/machinery/injector_test.go @@ -93,10 +93,14 @@ func (t *templateWithResource) InjectResource(res *resource.Resource) { } var _ = Describe("injector", func() { - tmp := templateBase{ - path: "my/path/to/file", - ifExistsAction: Error, - } + var tmp templateBase + + BeforeEach(func() { + tmp = templateBase{ + path: "my/path/to/file", + ifExistsAction: Error, + } + }) Context("injectInto", func() { Context("Config", func() { diff --git a/pkg/machinery/mixins_test.go b/pkg/machinery/mixins_test.go index dda7bdcc108..8b81cfc6ece 100644 --- a/pkg/machinery/mixins_test.go +++ b/pkg/machinery/mixins_test.go @@ -45,13 +45,17 @@ var _ = Describe("TemplateMixin", func() { body = "content" ) - tmp := mockTemplate{ - TemplateMixin: TemplateMixin{ - PathMixin: PathMixin{path}, - IfExistsActionMixin: IfExistsActionMixin{ifExistsAction}, - TemplateBody: body, - }, - } + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{ + TemplateMixin: TemplateMixin{ + PathMixin: PathMixin{path}, + IfExistsActionMixin: IfExistsActionMixin{ifExistsAction}, + TemplateBody: body, + }, + } + }) Context("GetPath", func() { It("should return the path", func() { @@ -75,11 +79,15 @@ var _ = Describe("TemplateMixin", func() { var _ = Describe("InserterMixin", func() { const path = "path/to/file.go" - tmp := mockInserter{ - InserterMixin: InserterMixin{ - PathMixin: PathMixin{path}, - }, - } + var tmp mockInserter + + BeforeEach(func() { + tmp = mockInserter{ + InserterMixin: InserterMixin{ + PathMixin: PathMixin{path}, + }, + } + }) Context("GetPath", func() { It("should return the path", func() { @@ -97,7 +105,11 @@ var _ = Describe("InserterMixin", func() { var _ = Describe("DomainMixin", func() { const domain = "my.domain" - tmp := mockTemplate{} + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{} + }) Context("InjectDomain", func() { It("should inject the provided domain", func() { @@ -110,7 +122,11 @@ var _ = Describe("DomainMixin", func() { var _ = Describe("RepositoryMixin", func() { const repo = "test" - tmp := mockTemplate{} + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{} + }) Context("InjectRepository", func() { It("should inject the provided repository", func() { @@ -123,7 +139,11 @@ var _ = Describe("RepositoryMixin", func() { var _ = Describe("ProjectNameMixin", func() { const name = "my project" - tmp := mockTemplate{} + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{} + }) Context("InjectProjectName", func() { It("should inject the provided project name", func() { @@ -134,7 +154,11 @@ var _ = Describe("ProjectNameMixin", func() { }) var _ = Describe("MultiGroupMixin", func() { - tmp := mockTemplate{} + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{} + }) Context("InjectMultiGroup", func() { It("should inject the provided multi group flag", func() { @@ -147,7 +171,11 @@ var _ = Describe("MultiGroupMixin", func() { var _ = Describe("BoilerplateMixin", func() { const boilerplate = "Copyright" - tmp := mockTemplate{} + var tmp mockTemplate + + BeforeEach(func() { + tmp = mockTemplate{} + }) Context("InjectBoilerplate", func() { It("should inject the provided boilerplate", func() { @@ -158,14 +186,21 @@ var _ = Describe("BoilerplateMixin", func() { }) var _ = Describe("ResourceMixin", func() { - res := &resource.Resource{GVK: resource.GVK{ - Group: "group", - Domain: "my.domain", - Version: "v1", - Kind: "Kind", - }} - - tmp := mockTemplate{} + var ( + res *resource.Resource + tmp mockTemplate + ) + + BeforeEach(func() { + res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + tmp = mockTemplate{} + }) Context("InjectResource", func() { It("should inject the provided resource", func() { diff --git a/pkg/machinery/scaffold.go b/pkg/machinery/scaffold.go index d47c4913ef9..1b6c3e33fb3 100644 --- a/pkg/machinery/scaffold.go +++ b/pkg/machinery/scaffold.go @@ -210,13 +210,13 @@ func doTemplate(t Template) ([]byte, error) { // Set the template body if _, err := temp.Parse(t.GetBody()); err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse template: %w", err) } // Execute the template out := &bytes.Buffer{} if err := temp.Execute(out, t); err != nil { - return nil, err + return nil, fmt.Errorf("failed to execute template: %w", err) } b := out.Bytes() @@ -225,7 +225,7 @@ func doTemplate(t Template) ([]byte, error) { if filepath.Ext(t.GetPath()) == ".go" { var err error if b, err = imports.Process(t.GetPath(), b, &options); err != nil { - return nil, err + return nil, fmt.Errorf("failed to process template: %w", err) } } @@ -236,7 +236,7 @@ func doTemplate(t Template) ([]byte, error) { func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { m, err := s.loadPreviousModel(i, models) if err != nil { - return err + return fmt.Errorf("failed to load previous model: %w", err) } // Get valid code fragments @@ -245,7 +245,7 @@ func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { // Remove code fragments that already were applied err = filterExistingValues(m.Contents, codeFragments) if err != nil { - return err + return fmt.Errorf("failed to filter existing values: %w", err) } // If no code fragment to insert, we are done @@ -255,7 +255,7 @@ func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { content, err := insertStrings(m.Contents, codeFragments) if err != nil { - return err + return fmt.Errorf("failed to insert values: %w", err) } // TODO(adirio): move go-formatting to write step @@ -263,7 +263,7 @@ func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { if ext := filepath.Ext(i.GetPath()); ext == ".go" { formattedContent, err = imports.Process(i.GetPath(), content, nil) if err != nil { - return err + return fmt.Errorf("failed to process formatted content: %w", err) } } @@ -277,9 +277,9 @@ func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { func (s Scaffold) loadPreviousModel(i Inserter, models map[string]*File) (*File, error) { path := i.GetPath() - // Lets see if we already have a model for this file + // Let's see if we already have a model for this file if m, found := models[path]; found { - // Check if there is already an scaffolded file + // Check if there is already a scaffolded file exists, err := afero.Exists(s.fs, path) if err != nil { return nil, ExistsFileError{err} @@ -365,7 +365,7 @@ func filterExistingValues(content string, codeFragmentsMap CodeFragmentsMap) err for _, codeFragment := range codeFragments { exists, err := codeFragmentExists(content, codeFragment) if err != nil { - return err + return fmt.Errorf("failed to check if code fragment exists: %w", err) } if !exists { codeFragmentsOut = append(codeFragmentsOut, codeFragment) @@ -401,8 +401,8 @@ func codeFragmentExists(content, codeFragment string) (exists bool, err error) { return true } - if err := scanMultiline(content, scanLines, scanFunc); err != nil { - return false, err + if scanMultilineErr := scanMultiline(content, scanLines, scanFunc); scanMultilineErr != nil { + return false, scanMultilineErr } return exists, nil @@ -418,10 +418,19 @@ func scanMultiline(content string, scanLines int, scanFunc func(contentGroup str if scanLines == 1 { for scanner.Scan() { if !scanFunc(strings.TrimSpace(scanner.Text())) { - return scanner.Err() + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan content: %w", err) + } + + return nil } } - return scanner.Err() + + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan content: %w", err) + } + + return nil } // Complex case. @@ -441,11 +450,19 @@ func scanMultiline(content string, scanLines int, scanFunc func(contentGroup str } if !scanFunc(strings.TrimSpace(sb.String())) { - return scanner.Err() + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan content: %w", err) + } + + return nil } } - return scanner.Err() + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan content: %w", err) + } + + return nil } func insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, error) { @@ -466,13 +483,13 @@ func insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, e _, _ = out.WriteString(line + "\n") // bytes.Buffer.WriteString always returns nil errors } if err := scanner.Err(); err != nil { - return nil, err + return nil, fmt.Errorf("failed to scan content: %w", err) } return out.Bytes(), nil } -func (s Scaffold) writeFile(f *File) (err error) { +func (s Scaffold) writeFile(f *File) error { // Check if the file to write already exists exists, err := afero.Exists(s.fs, f.Path) if err != nil { @@ -507,8 +524,8 @@ func (s Scaffold) writeFile(f *File) (err error) { } }() - if _, err := writer.Write([]byte(f.Contents)); err != nil { - return WriteFileError{err} + if _, writeErr := writer.Write([]byte(f.Contents)); writeErr != nil { + return WriteFileError{writeErr} } return nil diff --git a/pkg/machinery/scaffold_test.go b/pkg/machinery/scaffold_test.go index f45ed0fd45a..37f03e359cd 100644 --- a/pkg/machinery/scaffold_test.go +++ b/pkg/machinery/scaffold_test.go @@ -21,7 +21,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/spf13/afero" - cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" ) @@ -115,12 +114,12 @@ var _ = Describe("Scaffold", func() { ) var ( - testErr = errors.New("error text") - - s *Scaffold + testErr error + s *Scaffold ) BeforeEach(func() { + testErr = errors.New("error text") s = &Scaffold{fs: afero.NewMemMapFs()} }) @@ -168,29 +167,38 @@ var _ = Describe("Scaffold", func() { ) DescribeTable("file builders related errors", - func(errType interface{}, files ...Builder) { + func(setup func() ([]Builder, error)) { + files, errType := setup() + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) - Expect(errors.As(err, errType)).To(BeTrue()) + Expect(err).To(MatchError(errType)) }, - Entry("should fail if unable to validate a file builder", - &ValidateError{}, - fakeRequiresValidation{validateErr: testErr}, - ), - Entry("should fail if unable to set default values for a template", - &SetTemplateDefaultsError{}, - &fakeTemplate{err: testErr}, - ), - Entry("should fail if an unexpected previous model is found", - &ModelAlreadyExistsError{}, - &fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - &fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}}, - ), - Entry("should fail if behavior if-exists-action is not defined", - &UnknownIfExistsActionError{}, - &fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - &fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}}, - ), + Entry("should fail if unable to validate a file builder", func() ([]Builder, error) { + return []Builder{ + fakeRequiresValidation{validateErr: testErr}, + }, ValidateError{testErr} + }), + + Entry("should fail if unable to set default values for a template", func() ([]Builder, error) { + return []Builder{ + &fakeTemplate{err: testErr}, + }, SetTemplateDefaultsError{testErr} + }), + + Entry("should fail if an unexpected previous model is found", func() ([]Builder, error) { + return []Builder{ + &fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + &fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}}, + }, ModelAlreadyExistsError{path: path} + }), + Entry("should fail if behavior if-exists-action is not defined", func() ([]Builder, error) { + return []Builder{ + &fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + &fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}}, + }, UnknownIfExistsActionError{path: path, ifExistsAction: -1} + }), ) // Following errors are unwrapped, so we need to check for substrings @@ -408,20 +416,20 @@ func init() { ) DescribeTable("insert strings related errors", - func(errType interface{}, files ...Builder) { + func(errType error, files ...Builder) { Expect(afero.WriteFile(s.fs, path, []byte{}, 0o666)).To(Succeed()) err := s.Execute(files...) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, errType)).To(BeTrue()) + Expect(err).To(MatchError(errType)) }, Entry("should fail if inserting into a model that fails when a file exists and it does exist", - &FileAlreadyExistsError{}, + FileAlreadyExistsError{path: "filename"}, &fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", - &UnknownIfExistsActionError{}, + UnknownIfExistsActionError{path: "filename", ifExistsAction: -1}, &fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), @@ -460,7 +468,7 @@ func init() { body: content, }) Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &FileAlreadyExistsError{})).To(BeTrue()) + Expect(err).To(MatchError(FileAlreadyExistsError{path: path})) }) }) }) diff --git a/pkg/model/resource/api_test.go b/pkg/model/resource/api_test.go index fbce493e50a..440a47933d7 100644 --- a/pkg/model/resource/api_test.go +++ b/pkg/model/resource/api_test.go @@ -116,7 +116,13 @@ var _ = Describe("API", func() { Context("IsEmpty", func() { var ( - none = API{} + none API + cluster API + namespaced API + ) + + BeforeEach(func() { + none = API{} cluster = API{ CRDVersion: v1, } @@ -124,16 +130,18 @@ var _ = Describe("API", func() { CRDVersion: v1, Namespaced: true, } - ) + }) It("should return true fo an empty object", func() { Expect(none.IsEmpty()).To(BeTrue()) }) DescribeTable("should return false for non-empty objects", - func(api API) { Expect(api.IsEmpty()).To(BeFalse()) }, - Entry("cluster-scope", cluster), - Entry("namespace-scope", namespaced), + func(getAPI func() API) { + Expect(getAPI().IsEmpty()).To(BeFalse()) + }, + Entry("cluster-scope", func() API { return cluster }), + Entry("namespace-scope", func() API { return namespaced }), ) }) }) diff --git a/pkg/model/resource/gvk.go b/pkg/model/resource/gvk.go index c021fda5043..6bed8bd957b 100644 --- a/pkg/model/resource/gvk.go +++ b/pkg/model/resource/gvk.go @@ -57,7 +57,7 @@ func (gvk GVK) Validate() error { return errors.New(versionRequired) } if errs := validation.IsDNS1123Subdomain(gvk.Version); len(errs) > 0 && gvk.Version != versionInternal { - return fmt.Errorf("Version must respect DNS-1123 (was %s)", gvk.Version) + return fmt.Errorf("version must respect DNS-1123 (was %q)", gvk.Version) } // Check if kind has a valid DNS1035 label value diff --git a/pkg/model/resource/gvk_test.go b/pkg/model/resource/gvk_test.go index 3c3a52dfb90..9b6ca95f401 100644 --- a/pkg/model/resource/gvk_test.go +++ b/pkg/model/resource/gvk_test.go @@ -32,13 +32,21 @@ var _ = Describe("GVK", func() { internalVersion = "__internal" ) - gvk := GVK{Group: group, Domain: domain, Version: version, Kind: kind} + var gvk GVK + + BeforeEach(func() { + gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind} + }) Context("Validate", func() { DescribeTable("should pass valid GVKs", - func(gvk GVK) { Expect(gvk.Validate()).To(Succeed()) }, - Entry("Standard GVK", gvk), - Entry("Version (__internal)", GVK{Group: group, Domain: domain, Version: internalVersion, Kind: kind}), + func(get func() GVK) { + Expect(get().Validate()).To(Succeed()) + }, + Entry("Standard GVK", func() GVK { return gvk }), + Entry("Version (__internal)", func() GVK { + return GVK{Group: group, Domain: domain, Version: internalVersion, Kind: kind} + }), ) DescribeTable("should fail for invalid GVKs", @@ -68,10 +76,16 @@ var _ = Describe("GVK", func() { Context("QualifiedGroup", func() { DescribeTable("should return the correct string", - func(gvk GVK, qualifiedGroup string) { Expect(gvk.QualifiedGroup()).To(Equal(qualifiedGroup)) }, - Entry("fully qualified resource", gvk, group+"."+domain), - Entry("empty group name", GVK{Domain: domain, Version: version, Kind: kind}, domain), - Entry("empty domain", GVK{Group: group, Version: version, Kind: kind}, group), + func(get func() GVK, qualifiedGroup string) { + Expect(get().QualifiedGroup()).To(Equal(qualifiedGroup)) + }, + Entry("fully qualified resource", func() GVK { return gvk }, group+"."+domain), + Entry("empty group name", func() GVK { + return GVK{Domain: domain, Version: version, Kind: kind} + }, domain), + Entry("empty domain", func() GVK { + return GVK{Group: group, Version: version, Kind: kind} + }, group), ) }) @@ -81,11 +95,21 @@ var _ = Describe("GVK", func() { }) DescribeTable("should return false for different resources", - func(other GVK) { Expect(gvk.IsEqualTo(other)).To(BeFalse()) }, - Entry("different kind", GVK{Group: group, Domain: domain, Version: version, Kind: "Kind2"}), - Entry("different version", GVK{Group: group, Domain: domain, Version: "v2", Kind: kind}), - Entry("different domain", GVK{Group: group, Domain: "other.domain", Version: version, Kind: kind}), - Entry("different group", GVK{Group: "group2", Domain: domain, Version: version, Kind: kind}), + func(get func() GVK) { + Expect(gvk.IsEqualTo(get())).To(BeFalse()) + }, + Entry("different kind", func() GVK { + return GVK{Group: group, Domain: domain, Version: version, Kind: "Kind2"} + }), + Entry("different version", func() GVK { + return GVK{Group: group, Domain: domain, Version: "v2", Kind: kind} + }), + Entry("different domain", func() GVK { + return GVK{Group: group, Domain: "other.domain", Version: version, Kind: kind} + }), + Entry("different group", func() GVK { + return GVK{Group: "group2", Domain: domain, Version: version, Kind: kind} + }), ) }) }) diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index 84000e958f0..03c8ecc3455 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -158,7 +158,7 @@ func (r *Resource) Update(other Resource) error { } // Make sure we are not merging resources for different GVKs. - if !r.GVK.IsEqualTo(other.GVK) { + if !r.IsEqualTo(other.GVK) { return fmt.Errorf("unable to update a Resource (GVK %+v) with another with non-matching GVK %+v", r.GVK, other.GVK) } diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index 94b8d2eae6b..576946e6379 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -17,6 +17,8 @@ limitations under the License. package resource import ( + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -32,6 +34,11 @@ var _ = Describe("Resource", func() { ) var ( + gvk GVK + res Resource + ) + + BeforeEach(func() { gvk = GVK{ Group: group, Domain: domain, @@ -42,7 +49,7 @@ var _ = Describe("Resource", func() { GVK: gvk, Plural: plural, } - ) + }) Context("Validate", func() { It("should succeed for a valid Resource", func() { @@ -69,6 +76,13 @@ var _ = Describe("Resource", func() { ) var ( + resNoGroup Resource + resNoDomain Resource + resHyphenGroup Resource + resDotGroup Resource + ) + + BeforeEach(func() { resNoGroup = Resource{ GVK: GVK{ // Empty group @@ -101,24 +115,28 @@ var _ = Describe("Resource", func() { Kind: kind, }, } - ) + }) DescribeTable("PackageName should return the correct string", - func(res Resource, packageName string) { Expect(res.PackageName()).To(Equal(packageName)) }, - Entry("fully qualified resource", res, group), - Entry("empty group name", resNoGroup, safeDomain), - Entry("empty domain", resNoDomain, group), - Entry("hyphen-containing group", resHyphenGroup, safeGroup), - Entry("dot-containing group", resDotGroup, safeGroup), + func(getRes func() Resource, expected string) { + Expect(getRes().PackageName()).To(Equal(expected)) + }, + Entry("fully qualified resource", func() Resource { return res }, group), + Entry("empty group name", func() Resource { return resNoGroup }, safeDomain), + Entry("empty domain", func() Resource { return resNoDomain }, group), + Entry("hyphen-containing group", func() Resource { return resHyphenGroup }, safeGroup), + Entry("dot-containing group", func() Resource { return resDotGroup }, safeGroup), ) DescribeTable("ImportAlias", - func(res Resource, importAlias string) { Expect(res.ImportAlias()).To(Equal(importAlias)) }, - Entry("fully qualified resource", res, groupVersion), - Entry("empty group name", resNoGroup, domainVersion), - Entry("empty domain", resNoDomain, groupVersion), - Entry("hyphen-containing group", resHyphenGroup, safeAlias), - Entry("dot-containing group", resDotGroup, safeAlias), + func(getRes func() Resource, expected string) { + Expect(getRes().ImportAlias()).To(Equal(expected)) + }, + Entry("fully qualified resource", func() Resource { return res }, groupVersion), + Entry("empty group name", func() Resource { return resNoGroup }, domainVersion), + Entry("empty domain", func() Resource { return resNoDomain }, groupVersion), + Entry("hyphen-containing group", func() Resource { return resHyphenGroup }, safeAlias), + Entry("dot-containing group", func() Resource { return resDotGroup }, safeAlias), ) }) @@ -199,22 +217,24 @@ var _ = Describe("Resource", func() { webhookVersion = "v1" ) - res = Resource{ - GVK: gvk, - Plural: plural, - Path: path, - API: &API{ - CRDVersion: crdVersion, - Namespaced: true, - }, - Controller: true, - Webhooks: &Webhooks{ - WebhookVersion: webhookVersion, - Defaulting: true, - Validation: true, - Conversion: true, - }, - } + BeforeEach(func() { + res = Resource{ + GVK: gvk, + Plural: plural, + Path: path, + API: &API{ + CRDVersion: crdVersion, + Namespaced: true, + }, + Controller: true, + Webhooks: &Webhooks{ + WebhookVersion: webhookVersion, + Defaulting: true, + Validation: true, + Conversion: true, + }, + } + }) It("should return an exact copy", func() { other := res.Copy() @@ -423,16 +443,22 @@ var _ = Describe("Resource", func() { }) Context("Replacer", func() { - replacer := res.Replacer() + var replacer *strings.Replacer + + BeforeEach(func() { + replacer = res.Replacer() + }) DescribeTable("should replace the following strings", - func(pattern, result string) { Expect(replacer.Replace(pattern)).To(Equal(result)) }, - Entry("no pattern", "version", "version"), - Entry("pattern `%[group]`", "%[group]", res.Group), - Entry("pattern `%[version]`", "%[version]", res.Version), - Entry("pattern `%[kind]`", "%[kind]", "kind"), - Entry("pattern `%[plural]`", "%[plural]", res.Plural), - Entry("pattern `%[package-name]`", "%[package-name]", res.PackageName()), + func(pattern string, expected func() string) { + Expect(replacer.Replace(pattern)).To(Equal(expected())) + }, + Entry("no pattern", "version", func() string { return "version" }), + Entry("pattern `%[group]`", "%[group]", func() string { return res.Group }), + Entry("pattern `%[version]`", "%[version]", func() string { return res.Version }), + Entry("pattern `%[kind]`", "%[kind]", func() string { return "kind" }), + Entry("pattern `%[plural]`", "%[plural]", func() string { return res.Plural }), + Entry("pattern `%[package-name]`", "%[package-name]", func() string { return res.PackageName() }), ) }) }) diff --git a/pkg/model/resource/utils.go b/pkg/model/resource/utils.go index 31a6c83b6fa..8fd37e5a83c 100644 --- a/pkg/model/resource/utils.go +++ b/pkg/model/resource/utils.go @@ -39,8 +39,8 @@ func safeImport(unsafe string) string { safe := unsafe // Remove dashes and dots - safe = strings.Replace(safe, "-", "", -1) - safe = strings.Replace(safe, ".", "", -1) + safe = strings.ReplaceAll(safe, "-", "") + safe = strings.ReplaceAll(safe, ".", "") return safe } diff --git a/pkg/model/resource/webhooks_test.go b/pkg/model/resource/webhooks_test.go index fcc79db4b45..ae187b858cd 100644 --- a/pkg/model/resource/webhooks_test.go +++ b/pkg/model/resource/webhooks_test.go @@ -195,7 +195,20 @@ var _ = Describe("Webhooks", func() { Context("IsEmpty", func() { var ( - none = Webhooks{} + none Webhooks + defaulting Webhooks + validation Webhooks + conversion Webhooks + + defaultingAndValidation Webhooks + defaultingAndConversion Webhooks + validationAndConversion Webhooks + + all Webhooks + ) + + BeforeEach(func() { + none = Webhooks{} defaulting = Webhooks{ WebhookVersion: "v1", Defaulting: true, @@ -238,21 +251,23 @@ var _ = Describe("Webhooks", func() { Validation: true, Conversion: true, } - ) + }) It("should return true fo an empty object", func() { Expect(none.IsEmpty()).To(BeTrue()) }) DescribeTable("should return false for non-empty objects", - func(webhooks Webhooks) { Expect(webhooks.IsEmpty()).To(BeFalse()) }, - Entry("defaulting", defaulting), - Entry("validation", validation), - Entry("conversion", conversion), - Entry("defaulting and validation", defaultingAndValidation), - Entry("defaulting and conversion", defaultingAndConversion), - Entry("validation and conversion", validationAndConversion), - Entry("defaulting and validation and conversion", all), + func(get func() Webhooks) { + Expect(get().IsEmpty()).To(BeFalse()) + }, + Entry("defaulting", func() Webhooks { return defaulting }), + Entry("validation", func() Webhooks { return validation }), + Entry("conversion", func() Webhooks { return conversion }), + Entry("defaulting and validation", func() Webhooks { return defaultingAndValidation }), + Entry("defaulting and conversion", func() Webhooks { return defaultingAndConversion }), + Entry("validation and conversion", func() Webhooks { return validationAndConversion }), + Entry("defaulting and validation and conversion", func() Webhooks { return all }), ) }) }) diff --git a/pkg/model/stage/stage_test.go b/pkg/model/stage/stage_test.go index 7368e224ebb..88d13d1173a 100644 --- a/pkg/model/stage/stage_test.go +++ b/pkg/model/stage/stage_test.go @@ -20,76 +20,81 @@ import ( "sort" "testing" - g "github.com/onsi/ginkgo/v2" // An alias is required because Context is defined elsewhere in this package. + . "github.com/onsi/ginkgo/v2" // An alias is required because Context is defined elsewhere in this package. . "github.com/onsi/gomega" ) func TestStage(t *testing.T) { - RegisterFailHandler(g.Fail) - g.RunSpecs(t, "Stage Suite") + RegisterFailHandler(Fail) + RunSpecs(t, "Stage Suite") } -var _ = g.Describe("ParseStage", func() { - g.DescribeTable("should be correctly parsed for valid stage strings", +var _ = Describe("ParseStage", func() { + DescribeTable("should be correctly parsed for valid stage strings", func(str string, stage Stage) { s, err := ParseStage(str) Expect(err).NotTo(HaveOccurred()) Expect(s).To(Equal(stage)) }, - g.Entry("for alpha stage", "alpha", Alpha), - g.Entry("for beta stage", "beta", Beta), - g.Entry("for stable stage", "", Stable), + Entry("for alpha stage", "alpha", Alpha), + Entry("for beta stage", "beta", Beta), + Entry("for stable stage", "", Stable), ) - g.DescribeTable("should error when parsing invalid stage strings", + DescribeTable("should error when parsing invalid stage strings", func(str string) { _, err := ParseStage(str) Expect(err).To(HaveOccurred()) }, - g.Entry("passing a number as the stage string", "1"), - g.Entry("passing `gamma` as the stage string", "gamma"), - g.Entry("passing a dash-prefixed stage string", "-alpha"), + Entry("passing a number as the stage string", "1"), + Entry("passing `gamma` as the stage string", "gamma"), + Entry("passing a dash-prefixed stage string", "-alpha"), ) }) -var _ = g.Describe("Stage", func() { - g.Context("String", func() { - g.DescribeTable("should return the correct string value", +var _ = Describe("Stage", func() { + Context("String", func() { + DescribeTable("should return the correct string value", func(stage Stage, str string) { Expect(stage.String()).To(Equal(str)) }, - g.Entry("for alpha stage", Alpha, "alpha"), - g.Entry("for beta stage", Beta, "beta"), - g.Entry("for stable stage", Stable, ""), + Entry("for alpha stage", Alpha, "alpha"), + Entry("for beta stage", Beta, "beta"), + Entry("for stable stage", Stable, ""), ) - g.DescribeTable("should panic", + DescribeTable("should panic", func(stage Stage) { Expect(func() { _ = stage.String() }).To(Panic()) }, - g.Entry("for stage 34", Stage(34)), - g.Entry("for stage 75", Stage(75)), - g.Entry("for stage 123", Stage(123)), - g.Entry("for stage 255", Stage(255)), + Entry("for stage 34", Stage(34)), + Entry("for stage 75", Stage(75)), + Entry("for stage 123", Stage(123)), + Entry("for stage 255", Stage(255)), ) }) - g.Context("Validate", func() { - g.DescribeTable("should validate existing stages", + Context("Validate", func() { + DescribeTable("should validate existing stages", func(stage Stage) { Expect(stage.Validate()).To(Succeed()) }, - g.Entry("for alpha stage", Alpha), - g.Entry("for beta stage", Beta), - g.Entry("for stable stage", Stable), + Entry("for alpha stage", Alpha), + Entry("for beta stage", Beta), + Entry("for stable stage", Stable), ) - g.DescribeTable("should fail for non-existing stages", + DescribeTable("should fail for non-existing stages", func(stage Stage) { Expect(stage.Validate()).NotTo(Succeed()) }, - g.Entry("for stage 34", Stage(34)), - g.Entry("for stage 75", Stage(75)), - g.Entry("for stage 123", Stage(123)), - g.Entry("for stage 255", Stage(255)), + Entry("for stage 34", Stage(34)), + Entry("for stage 75", Stage(75)), + Entry("for stage 123", Stage(123)), + Entry("for stage 255", Stage(255)), ) }) - g.Context("Compare", func() { + Context("Compare", func() { // Test Stage.Compare by sorting a list var ( + stages []Stage + sortedStages []Stage + ) + + BeforeEach(func() { stages = []Stage{ Stable, Alpha, @@ -107,9 +112,9 @@ var _ = g.Describe("Stage", func() { Stable, Stable, } - ) + }) - g.It("sorts stages correctly", func() { + It("sorts stages correctly", func() { sort.Slice(stages, func(i int, j int) bool { return stages[i].Compare(stages[j]) == -1 }) @@ -117,15 +122,15 @@ var _ = g.Describe("Stage", func() { }) }) - g.Context("IsStable", func() { - g.It("should return true for stable stage", func() { + Context("IsStable", func() { + It("should return true for stable stage", func() { Expect(Stable.IsStable()).To(BeTrue()) }) - g.DescribeTable("should return false for any unstable stage", + DescribeTable("should return false for any unstable stage", func(stage Stage) { Expect(stage.IsStable()).To(BeFalse()) }, - g.Entry("for alpha stage", Alpha), - g.Entry("for beta stage", Beta), + Entry("for alpha stage", Alpha), + Entry("for beta stage", Beta), ) }) }) diff --git a/pkg/plugin/bundle_test.go b/pkg/plugin/bundle_test.go index 5f8ea14ce8f..18c718b626e 100644 --- a/pkg/plugin/bundle_test.go +++ b/pkg/plugin/bundle_test.go @@ -32,7 +32,16 @@ var _ = Describe("Bundle", func() { ) var ( - version = Version{Number: 1} + v Version + + p1 mockPlugin + p2 mockPlugin + p3 mockPlugin + p4 mockPlugin + ) + + BeforeEach(func() { + v = Version{Number: 1} p1 = mockPlugin{supportedProjectVersions: []config.Version{ {Number: 1}, @@ -53,7 +62,7 @@ var _ = Describe("Bundle", func() { {Number: 2}, {Number: 3}, }} - ) + }) Context("NewBundle", func() { It("should succeed for plugins with common supported project versions", func() { @@ -69,11 +78,11 @@ var _ = Describe("Bundle", func() { } { b, err := NewBundleWithOptions(WithName(name), - WithVersion(version), + WithVersion(v), WithPlugins(plugins...)) Expect(err).NotTo(HaveOccurred()) Expect(b.Name()).To(Equal(name)) - Expect(b.Version().Compare(version)).To(Equal(0)) + Expect(b.Version().Compare(v)).To(Equal(0)) versions := b.SupportedProjectVersions() sort.Slice(versions, func(i int, j int) bool { return versions[i].Compare(versions[j]) == -1 @@ -92,11 +101,11 @@ var _ = Describe("Bundle", func() { var err error plugins := []Plugin{p1, p2, p3} a, err = NewBundleWithOptions(WithName("a"), - WithVersion(version), + WithVersion(v), WithPlugins(p1, p2)) Expect(err).NotTo(HaveOccurred()) b, err = NewBundleWithOptions(WithName("b"), - WithVersion(version), + WithVersion(v), WithPlugins(a, p3)) Expect(err).NotTo(HaveOccurred()) versions := b.SupportedProjectVersions() @@ -121,7 +130,7 @@ var _ = Describe("Bundle", func() { {p1, p2, p3, p4}, } { _, err := NewBundleWithOptions(WithName(name), - WithVersion(version), + WithVersion(v), WithPlugins(plugins...)) Expect(err).To(HaveOccurred()) @@ -142,13 +151,13 @@ var _ = Describe("Bundle", func() { {p1, p3, p4}, } { b, err := NewBundleWithOptions(WithName(name), - WithVersion(version), + WithVersion(v), WithDeprecationMessage(""), WithPlugins(plugins...), ) Expect(err).NotTo(HaveOccurred()) Expect(b.Name()).To(Equal(name)) - Expect(b.Version().Compare(version)).To(Equal(0)) + Expect(b.Version().Compare(v)).To(Equal(0)) versions := b.SupportedProjectVersions() sort.Slice(versions, func(i int, j int) bool { return versions[i].Compare(versions[j]) == -1 @@ -167,13 +176,13 @@ var _ = Describe("Bundle", func() { var err error plugins := []Plugin{p1, p2, p3} a, err = NewBundleWithOptions(WithName("a"), - WithVersion(version), + WithVersion(v), WithDeprecationMessage(""), WithPlugins(p1, p2), ) Expect(err).NotTo(HaveOccurred()) b, err = NewBundleWithOptions(WithName("b"), - WithVersion(version), + WithVersion(v), WithDeprecationMessage(""), WithPlugins(a, p3), ) @@ -200,7 +209,7 @@ var _ = Describe("Bundle", func() { {p1, p2, p3, p4}, } { _, err := NewBundleWithOptions(WithName(name), - WithVersion(version), + WithVersion(v), WithDeprecationMessage(""), WithPlugins(plugins...), ) diff --git a/pkg/plugin/errors_test.go b/pkg/plugin/errors_test.go index aeea88e281b..d0e2f041f09 100644 --- a/pkg/plugin/errors_test.go +++ b/pkg/plugin/errors_test.go @@ -22,10 +22,14 @@ import ( ) var _ = Describe("PluginKeyNotFoundError", func() { - err := ExitError{ - Plugin: "go.kubebuilder.io/v1", - Reason: "skipping plugin", - } + var err ExitError + + BeforeEach(func() { + err = ExitError{ + Plugin: "go.kubebuilder.io/v1", + Reason: "skipping plugin", + } + }) Context("Error", func() { It("should return the correct error message", func() { diff --git a/pkg/plugin/filter_test.go b/pkg/plugin/filter_test.go index 555a35d3c92..f393c66a2c3 100644 --- a/pkg/plugin/filter_test.go +++ b/pkg/plugin/filter_test.go @@ -23,61 +23,68 @@ import ( "sigs.k8s.io/kubebuilder/v4/pkg/config" ) -var ( - p1 = mockPlugin{ - name: "go.kubebuilder.io", - version: Version{Number: 2}, - supportedProjectVersions: []config.Version{{Number: 2}, {Number: 3}}, - } - p2 = mockPlugin{ - name: "go.kubebuilder.io", - version: Version{Number: 3}, - supportedProjectVersions: []config.Version{{Number: 3}}, - } - p3 = mockPlugin{ - name: "example.kubebuilder.io", - version: Version{Number: 1}, - supportedProjectVersions: []config.Version{{Number: 2}}, - } - p4 = mockPlugin{ - name: "test.kubebuilder.io", - version: Version{Number: 1}, - supportedProjectVersions: []config.Version{{Number: 3}}, - } - p5 = mockPlugin{ - name: "go.test.domain", - version: Version{Number: 2}, - supportedProjectVersions: []config.Version{{Number: 2}}, - } +var _ = Describe("FilterPlugins", func() { + var ( + p1 mockPlugin + p2 mockPlugin + p3 mockPlugin + p4 mockPlugin + p5 mockPlugin + allPlugins []Plugin + ) - allPlugins = []Plugin{p1, p2, p3, p4, p5} -) + BeforeEach(func() { + p1 = mockPlugin{ + name: "go.kubebuilder.io", + version: Version{Number: 2}, + supportedProjectVersions: []config.Version{{Number: 2}, {Number: 3}}, + } + p2 = mockPlugin{ + name: "go.kubebuilder.io", + version: Version{Number: 3}, + supportedProjectVersions: []config.Version{{Number: 3}}, + } + p3 = mockPlugin{ + name: "example.kubebuilder.io", + version: Version{Number: 1}, + supportedProjectVersions: []config.Version{{Number: 2}}, + } + p4 = mockPlugin{ + name: "test.kubebuilder.io", + version: Version{Number: 1}, + supportedProjectVersions: []config.Version{{Number: 3}}, + } + p5 = mockPlugin{ + name: "go.test.domain", + version: Version{Number: 2}, + supportedProjectVersions: []config.Version{{Number: 2}}, + } -var _ = Describe("FilterPluginsByKey", func() { - DescribeTable("should filter", - func(key string, plugins []Plugin) { + allPlugins = []Plugin{p1, p2, p3, p4, p5} + }) + + DescribeTable("should filter by key", + func(key string, expectedPlugins func() []Plugin) { filtered, err := FilterPluginsByKey(allPlugins, key) Expect(err).NotTo(HaveOccurred()) - Expect(filtered).To(Equal(plugins)) + Expect(filtered).To(Equal(expectedPlugins())) }, - Entry("go plugins", "go", []Plugin{p1, p2, p5}), - Entry("go plugins (kubebuilder domain)", "go.kubebuilder", []Plugin{p1, p2}), - Entry("go v2 plugins", "go/v2", []Plugin{p1, p5}), - Entry("go v2 plugins (kubebuilder domain)", "go.kubebuilder/v2", []Plugin{p1}), + Entry("go plugins", "go", func() []Plugin { return []Plugin{p1, p2, p5} }), + Entry("go plugins (kubebuilder domain)", "go.kubebuilder", func() []Plugin { return []Plugin{p1, p2} }), + Entry("go v2 plugins", "go/v2", func() []Plugin { return []Plugin{p1, p5} }), + Entry("go v2 plugins (kubebuilder domain)", "go.kubebuilder/v2", func() []Plugin { return []Plugin{p1} }), ) It("should fail for invalid versions", func() { _, err := FilterPluginsByKey(allPlugins, "go/a") Expect(err).To(HaveOccurred()) }) -}) -var _ = Describe("FilterPluginsByKey", func() { - DescribeTable("should filter", - func(projectVersion config.Version, plugins []Plugin) { - Expect(FilterPluginsByProjectVersion(allPlugins, projectVersion)).To(Equal(plugins)) + DescribeTable("should filter by project version", + func(projectVersion config.Version, expectedPlugins func() []Plugin) { + Expect(FilterPluginsByProjectVersion(allPlugins, projectVersion)).To(Equal(expectedPlugins())) }, - Entry("project v2 plugins", config.Version{Number: 2}, []Plugin{p1, p3, p5}), - Entry("project v3 plugins", config.Version{Number: 3}, []Plugin{p1, p2, p4}), + Entry("project v2 plugins", config.Version{Number: 2}, func() []Plugin { return []Plugin{p1, p3, p5} }), + Entry("project v3 plugins", config.Version{Number: 3}, func() []Plugin { return []Plugin{p1, p2, p4} }), ) }) diff --git a/pkg/plugin/helpers.go b/pkg/plugin/helpers.go index 4898358c7bf..304bd1e4637 100644 --- a/pkg/plugin/helpers.go +++ b/pkg/plugin/helpers.go @@ -43,17 +43,17 @@ func SplitKey(key string) (string, string) { // Validate ensures a Plugin is valid. func Validate(p Plugin) error { if err := validateName(p.Name()); err != nil { - return fmt.Errorf("invalid plugin name %q: %v", p.Name(), err) + return fmt.Errorf("invalid plugin name %q: %w", p.Name(), err) } if err := p.Version().Validate(); err != nil { - return fmt.Errorf("invalid plugin version %q: %v", p.Version(), err) + return fmt.Errorf("invalid plugin version %q: %w", p.Version(), err) } if len(p.SupportedProjectVersions()) == 0 { return fmt.Errorf("plugin %q must support at least one project version", KeyFor(p)) } for _, projectVersion := range p.SupportedProjectVersions() { if err := projectVersion.Validate(); err != nil { - return fmt.Errorf("plugin %q supports an invalid project version %q: %v", KeyFor(p), projectVersion, err) + return fmt.Errorf("plugin %q supports an invalid project version %q: %w", KeyFor(p), projectVersion, err) } } return nil @@ -63,13 +63,13 @@ func Validate(p Plugin) error { func ValidateKey(key string) error { name, version := SplitKey(key) if err := validateName(name); err != nil { - return fmt.Errorf("invalid plugin name %q: %v", name, err) + return fmt.Errorf("invalid plugin name %q: %w", name, err) } // CLI-set plugins do not have to contain a version. if version != "" { var v Version if err := v.Parse(version); err != nil { - return fmt.Errorf("invalid plugin version %q: %v", version, err) + return fmt.Errorf("invalid plugin version %q: %w", version, err) } } return nil diff --git a/pkg/plugin/helpers_test.go b/pkg/plugin/helpers_test.go index b3541ed6d88..557a9f04871 100644 --- a/pkg/plugin/helpers_test.go +++ b/pkg/plugin/helpers_test.go @@ -116,9 +116,13 @@ var _ = Describe("ValidateKey", func() { }) var _ = Describe("SupportsVersion", func() { - plugin := mockPlugin{ - supportedProjectVersions: supportedProjectVersions, - } + var plugin mockPlugin + + BeforeEach(func() { + plugin = mockPlugin{ + supportedProjectVersions: supportedProjectVersions, + } + }) It("should return true for supported versions", func() { Expect(SupportsVersion(plugin, config.Version{Number: 2})).To(BeTrue()) diff --git a/pkg/plugin/util/exec.go b/pkg/plugin/util/exec.go index 806b17faf83..9e0afeab32f 100644 --- a/pkg/plugin/util/exec.go +++ b/pkg/plugin/util/exec.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "fmt" "os" "os/exec" "strings" @@ -30,5 +31,10 @@ func RunCmd(msg, cmd string, args ...string) error { c.Stdout = os.Stdout c.Stderr = os.Stderr log.Println(msg + ":\n$ " + strings.Join(c.Args, " ")) - return c.Run() + + if err := c.Run(); err != nil { + return fmt.Errorf("error running %q: %w", cmd, err) + } + + return nil } diff --git a/pkg/plugin/util/util.go b/pkg/plugin/util/util.go index 983b358f776..322de98dcd4 100644 --- a/pkg/plugin/util/util.go +++ b/pkg/plugin/util/util.go @@ -41,7 +41,7 @@ func RandomSuffix() (string, error) { bi := new(big.Int) r, err := rand.Int(rand.Reader, bi.SetInt64(int64(len(source)))) if err != nil { - return "", err + return "", fmt.Errorf("failed to generate random number: %w", err) } res[i] = source[r.Int64()] } @@ -67,7 +67,7 @@ func InsertCode(filename, target, code string) error { //nolint:gosec // false positive contents, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } idx := strings.Index(string(contents), target) if idx == -1 { @@ -75,15 +75,19 @@ func InsertCode(filename, target, code string) error { } out := string(contents[:idx+len(target)]) + code + string(contents[idx+len(target):]) //nolint:gosec // false positive - return os.WriteFile(filename, []byte(out), 0o644) + if errWriteFile := os.WriteFile(filename, []byte(out), 0o644); errWriteFile != nil { + return fmt.Errorf("failed to write file %q: %w", filename, errWriteFile) + } + + return nil } -// InsertCodeIfNotExist insert code if it does not already exists +// InsertCodeIfNotExist insert code if it does not already exist func InsertCodeIfNotExist(filename, target, code string) error { //nolint:gosec // false positive contents, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } idx := strings.Index(string(contents), code) @@ -98,7 +102,7 @@ func InsertCodeIfNotExist(filename, target, code string) error { func AppendCodeIfNotExist(filename, code string) error { contents, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } if strings.Contains(string(contents), code) { @@ -112,7 +116,7 @@ func AppendCodeIfNotExist(filename, code string) error { func AppendCodeAtTheEnd(filename, code string) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644) if err != nil { - return err + return fmt.Errorf("failed to open file %q: %w", filename, err) } defer func() { if err = f.Close(); err != nil { @@ -120,8 +124,11 @@ func AppendCodeAtTheEnd(filename, code string) error { } }() - _, err = f.WriteString(code) - return err + if _, errWriteString := f.WriteString(code); errWriteString != nil { + return fmt.Errorf("failed to write to file %q: %w", filename, errWriteString) + } + + return nil } // UncommentCode searches for target in the file and remove the comment prefix @@ -130,19 +137,19 @@ func UncommentCode(filename, target, prefix string) error { //nolint:gosec // false positive content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to file %q: %w", filename, err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -151,23 +158,26 @@ func UncommentCode(filename, target, prefix string) error { } for { if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { - return err + return fmt.Errorf("failed to write to file %q: %w", filename, err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } if _, err = out.WriteString("\n"); err != nil { - return err + return fmt.Errorf("failed to write to file %q: %w", filename, err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to file %q: %w", filename, err) } //nolint:gosec // false positive - return os.WriteFile(filename, out.Bytes(), 0o644) + if err = os.WriteFile(filename, out.Bytes(), 0o644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } // CommentCode searches for target in the file and adds the comment prefix @@ -176,95 +186,100 @@ func CommentCode(filename, target, prefix string) error { // Read the file content content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) // Find the target code to be commented idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be commented", target) + return fmt.Errorf("unable to find the code %q to be commented", target) } // Create a buffer to hold the modified content out := new(bytes.Buffer) - _, err = out.Write(content[:idx]) - if err != nil { - return err + if _, err = out.Write(content[:idx]); err != nil { + return fmt.Errorf("failed to write to file %q: %w", filename, err) } // Add the comment prefix to each line of the target code scanner := bufio.NewScanner(bytes.NewBufferString(target)) for scanner.Scan() { if _, err = out.WriteString(prefix + scanner.Text() + "\n"); err != nil { - return err + return fmt.Errorf("failed to write to file %q: %w", filename, err) } } // Write the rest of the file content - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to file %q: %w", filename, err) } // Write the modified content back to the file - return os.WriteFile(filename, out.Bytes(), 0o644) + if err = os.WriteFile(filename, out.Bytes(), 0o644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } -// EnsureExistAndReplace check if the content exists and then do the replace +// EnsureExistAndReplace check if the content exists and then do the replacement func EnsureExistAndReplace(input, match, replace string) (string, error) { if !strings.Contains(input, match) { return "", fmt.Errorf("can't find %q", match) } - return strings.Replace(input, match, replace, -1), nil + return strings.ReplaceAll(input, match, replace), nil } // ReplaceInFile replaces all instances of old with new in the file at path. func ReplaceInFile(path, oldValue, newValue string) error { info, err := os.Stat(path) if err != nil { - return err + return fmt.Errorf("failed to stat file %q: %w", path, err) } //nolint:gosec // false positive b, err := os.ReadFile(path) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", path, err) } if !strings.Contains(string(b), oldValue) { return errors.New("unable to find the content to be replaced") } - s := strings.Replace(string(b), oldValue, newValue, -1) - err = os.WriteFile(path, []byte(s), info.Mode()) - if err != nil { - return err + s := strings.ReplaceAll(string(b), oldValue, newValue) + if err = os.WriteFile(path, []byte(s), info.Mode()); err != nil { + return fmt.Errorf("failed to write file %q: %w", path, err) } return nil } // ReplaceRegexInFile finds all strings that match `match` and replaces them // with `replace` in the file at path. +// +// This function is currently unused in the Kubebuilder codebase, +// but is used by other projects and may be used in Kubebuilder in the future. func ReplaceRegexInFile(path, match, replace string) error { matcher, err := regexp.Compile(match) if err != nil { - return err + return fmt.Errorf("failed to compile regular expression %q: %w", match, err) } info, err := os.Stat(path) if err != nil { - return err + return fmt.Errorf("failed to stat file %q: %w", path, err) } //nolint:gosec // false positive b, err := os.ReadFile(path) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", path, err) } s := matcher.ReplaceAllString(string(b), replace) if s == string(b) { return errors.New("unable to find the content to be replaced") } - err = os.WriteFile(path, []byte(s), info.Mode()) - if err != nil { - return err + + if err = os.WriteFile(path, []byte(s), info.Mode()); err != nil { + return fmt.Errorf("failed to write file %q: %w", path, err) } + return nil } @@ -273,7 +288,7 @@ func HasFileContentWith(path, text string) (bool, error) { //nolint:gosec contents, err := os.ReadFile(path) if err != nil { - return false, err + return false, fmt.Errorf("failed to read file %q: %w", path, err) } return strings.Contains(string(contents), text), nil diff --git a/pkg/plugin/util/util_test.go b/pkg/plugin/util/util_test.go index 3e2a17957d9..d487fa22367 100644 --- a/pkg/plugin/util/util_test.go +++ b/pkg/plugin/util/util_test.go @@ -26,10 +26,14 @@ import ( var _ = Describe("Cover plugin util helpers", func() { Describe("InsertCode", Ordered, func() { - path := filepath.Join("testdata", "exampleFile.txt") - var content []byte + var ( + content []byte + path string + ) BeforeAll(func() { + path = filepath.Join("testdata", "exampleFile.txt") + err := os.MkdirAll("testdata", 0o755) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/plugin/version.go b/pkg/plugin/version.go index c01d71be7c1..eeadf35155a 100644 --- a/pkg/plugin/version.go +++ b/pkg/plugin/version.go @@ -54,12 +54,12 @@ func (v *Version) Parse(version string) error { if n, errParse := strconv.Atoi(version); errParse == nil && n < 0 { return errNegative } - return err + return fmt.Errorf("error converting version number %q: %w", substrings[0], err) } if len(substrings) > 1 { if err = v.Stage.Parse(substrings[1]); err != nil { - return err + return fmt.Errorf("error parsing stage %q: %w", substrings[1], err) } } @@ -81,7 +81,11 @@ func (v Version) Validate() error { return errNegative } - return v.Stage.Validate() + if err := v.Stage.Validate(); err != nil { + return fmt.Errorf("error validating stage %q: %w", v.Stage, err) + } + + return nil } // Compare returns -1 if v < other, 0 if v == other, and 1 if v > other. diff --git a/pkg/plugin/version_test.go b/pkg/plugin/version_test.go index 1fe8ff90734..f5b41dfc376 100644 --- a/pkg/plugin/version_test.go +++ b/pkg/plugin/version_test.go @@ -121,6 +121,11 @@ var _ = Describe("Version", func() { Context("Compare", func() { // Test Compare() by sorting a list. var ( + versions []Version + sortedVersions []Version + ) + + BeforeEach(func() { versions = []Version{ {Number: 2, Stage: stage.Alpha}, {Number: 44, Stage: stage.Alpha}, @@ -146,7 +151,7 @@ var _ = Describe("Version", func() { {Number: 44, Stage: stage.Alpha}, {Number: 44, Stage: stage.Alpha}, } - ) + }) It("sorts a valid list of versions correctly", func() { sort.Slice(versions, func(i int, j int) bool { diff --git a/pkg/plugins/common/kustomize/v2/api.go b/pkg/plugins/common/kustomize/v2/api.go index 5ca0ca6197d..67cfc1d949b 100644 --- a/pkg/plugins/common/kustomize/v2/api.go +++ b/pkg/plugins/common/kustomize/v2/api.go @@ -17,6 +17,8 @@ limitations under the License. package v2 import ( + "fmt" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds" @@ -34,5 +36,9 @@ func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { } scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("failed to scaffold api subcommand: %w", err) + } + + return nil } diff --git a/pkg/plugins/common/kustomize/v2/create.go b/pkg/plugins/common/kustomize/v2/create.go index d52221e3bc8..f0f32e7a028 100644 --- a/pkg/plugins/common/kustomize/v2/create.go +++ b/pkg/plugins/common/kustomize/v2/create.go @@ -17,6 +17,7 @@ limitations under the License. package v2 import ( + "fmt" "strconv" "github.com/spf13/pflag" @@ -50,7 +51,7 @@ func (p *createSubcommand) InjectResource(res *resource.Resource) error { func (p *createSubcommand) configure() (err error) { if forceFlag := p.flagSet.Lookup("force"); forceFlag != nil { if p.force, err = strconv.ParseBool(forceFlag.Value.String()); err != nil { - return err + return fmt.Errorf("invalid value for --force %s: %w", forceFlag.Value.String(), err) } } return nil diff --git a/pkg/plugins/common/kustomize/v2/init.go b/pkg/plugins/common/kustomize/v2/init.go index 2143145d7d0..e4b7ba9bc49 100644 --- a/pkg/plugins/common/kustomize/v2/init.go +++ b/pkg/plugins/common/kustomize/v2/init.go @@ -65,26 +65,35 @@ func (p *initSubcommand) InjectConfig(c config.Config) error { p.config = c if err := p.config.SetDomain(p.domain); err != nil { - return err + return fmt.Errorf("error setting domain: %w", err) } // Assign a default project name if p.name == "" { dir, err := os.Getwd() if err != nil { - return fmt.Errorf("error getting current directory: %v", err) + return fmt.Errorf("error getting current directory: %w", err) } p.name = strings.ToLower(filepath.Base(dir)) } // Check if the project name is a valid k8s namespace (DNS 1123 label). if err := validation.IsDNS1123Label(p.name); err != nil { - return fmt.Errorf("project name (%s) is invalid: %v", p.name, err) + return fmt.Errorf("project name %q is invalid: %v", p.name, err) } - return p.config.SetProjectName(p.name) + + if err := p.config.SetProjectName(p.name); err != nil { + return fmt.Errorf("error setting project name: %w", err) + } + + return nil } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder := scaffolds.NewInitScaffolder(p.config) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("failed to scaffold init subcommand: %w", err) + } + + return nil } diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/api.go b/pkg/plugins/common/kustomize/v2/scaffolds/api.go index ec15b31417c..5317d3ab4df 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/api.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/api.go @@ -81,18 +81,16 @@ func (s *apiScaffolder) Scaffold() error { &crd.Kustomization{}, &crd.KustomizeConfig{}, ); err != nil { - return fmt.Errorf("error scaffolding kustomize API manifests: %v", err) + return fmt.Errorf("error scaffolding kustomize API manifests: %w", err) } // If the gvk is non-empty if s.resource.Group != "" || s.resource.Version != "" || s.resource.Kind != "" { if err := scaffold.Execute(&samples.Kustomization{}); err != nil { - return fmt.Errorf("error scaffolding manifests: %v", err) + return fmt.Errorf("error scaffolding manifests: %w", err) } } - //nolint:goconst - kustomizeFilePath := "config/default/kustomization.yaml" err := pluginutil.UncommentCode(kustomizeFilePath, "#- ../crd", `#`) if err != nil { hasCRUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, "- ../crd") @@ -102,10 +100,12 @@ func (s *apiScaffolder) Scaffold() error { } } + comment := fmt.Sprintf(adminEditViewRulesCommentFragment, s.config.GetProjectName()) + // Add scaffolded CRD Admin, Editor and Viewer roles in config/rbac/kustomization.yaml rbacKustomizeFilePath := "config/rbac/kustomization.yaml" err = pluginutil.AppendCodeIfNotExist(rbacKustomizeFilePath, - adminEditViewRulesCommentFragment) + comment) if err != nil { log.Errorf("Unable to append the admin/edit/view roles comment in the file "+ "%s.", rbacKustomizeFilePath) @@ -114,7 +114,7 @@ func (s *apiScaffolder) Scaffold() error { if s.config.IsMultiGroup() && s.resource.Group != "" { crdName = strings.ToLower(s.resource.Group) + "_" + crdName } - err = pluginutil.InsertCodeIfNotExist(rbacKustomizeFilePath, adminEditViewRulesCommentFragment, + err = pluginutil.InsertCodeIfNotExist(rbacKustomizeFilePath, comment, fmt.Sprintf("\n- %[1]s_admin_role.yaml\n- %[1]s_editor_role.yaml\n- %[1]s_viewer_role.yaml", crdName)) if err != nil { log.Errorf("Unable to add Admin, Editor and Viewer roles in the file "+ @@ -136,5 +136,5 @@ func (s *apiScaffolder) Scaffold() error { const adminEditViewRulesCommentFragment = `# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are -# not used by the {{ .ProjectName }} itself. You can comment the following lines +# not used by the %s itself. You can comment the following lines # if you do not want those helpers be installed with your Project.` diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/init.go b/pkg/plugins/common/kustomize/v2/scaffolds/init.go index 2d60c69dfa0..67d95962cf3 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/init.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/init.go @@ -17,6 +17,8 @@ limitations under the License. package scaffolds import ( + "fmt" + log "github.com/sirupsen/logrus" "sigs.k8s.io/kubebuilder/v4/pkg/config" @@ -24,7 +26,7 @@ import ( "sigs.k8s.io/kubebuilder/v4/pkg/plugins" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager" - network_policy "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy" + networkpolicy "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/prometheus" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac" ) @@ -81,12 +83,16 @@ func (s *initScaffolder) Scaffold() error { &kdefault.CertManagerMetricsPatch{}, &manager.Config{Image: imageName}, &kdefault.Kustomization{}, - &network_policy.Kustomization{}, - &network_policy.PolicyAllowMetrics{}, + &networkpolicy.Kustomization{}, + &networkpolicy.PolicyAllowMetrics{}, &prometheus.Kustomization{}, &prometheus.Monitor{}, &prometheus.ServiceMonitorPatch{}, } - return scaffold.Execute(templates...) + if err := scaffold.Execute(templates...); err != nil { + return fmt.Errorf("failed to scaffold kustomize manifests: %w", err) + } + + return nil } diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization.go index 5d327201ffb..c07010b9b89 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -131,7 +131,7 @@ patches: # delimiter: '.' # index: 0 # create: true -# + # - source: # kind: Service # version: v1 @@ -161,7 +161,7 @@ patches: # delimiter: '.' # index: 1 # create: true -# + # - source: # Uncomment the following block if you have any webhook # kind: Service # version: v1 @@ -198,7 +198,7 @@ patches: # delimiter: '.' # index: 1 # create: true -# + # - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) # kind: Certificate # group: cert-manager.io @@ -229,7 +229,7 @@ patches: # delimiter: '/' # index: 1 # create: true -# + # - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) # kind: Certificate # group: cert-manager.io @@ -260,7 +260,7 @@ patches: # delimiter: '/' # index: 1 # create: true -# + # - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) # kind: Certificate # group: cert-manager.io diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go index 252374c8169..de91d28a6ea 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go @@ -113,6 +113,7 @@ spec: name: manager ports: [] securityContext: + readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: diff --git a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go index d5ad768ba9c..8909bae7056 100644 --- a/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go +++ b/pkg/plugins/common/kustomize/v2/scaffolds/webhook.go @@ -17,24 +17,31 @@ limitations under the License. package scaffolds import ( + "errors" "fmt" log "github.com/sirupsen/logrus" "sigs.k8s.io/kubebuilder/v4/pkg/config" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" "sigs.k8s.io/kubebuilder/v4/pkg/plugins" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault" - network_policy "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy" + networkpolicy "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/webhook" ) var _ plugins.Scaffolder = &webhookScaffolder{} +const ( + kustomizeFilePath = "config/default/kustomization.yaml" + kustomizeCRDFilePath = "config/crd/kustomization.yaml" +) + type webhookScaffolder struct { config config.Config resource resource.Resource @@ -88,7 +95,7 @@ func (s *webhookScaffolder) Scaffold() error { &certmanager.MetricsCertificate{}, &certmanager.Kustomization{}, &certmanager.KustomizeConfig{}, - &network_policy.PolicyAllowWebhooks{}, + &networkpolicy.PolicyAllowWebhooks{}, } // Only scaffold the following patches if is a conversion webhook @@ -102,23 +109,232 @@ func (s *webhookScaffolder) Scaffold() error { } if err := scaffold.Execute(buildScaffold...); err != nil { - return fmt.Errorf("error scaffolding kustomize webhook manifests: %v", err) + return fmt.Errorf("error scaffolding kustomize webhook manifests: %w", err) } - policyKustomizeFilePath := "config/network-policy/kustomization.yaml" - err := pluginutil.InsertCodeIfNotExist(policyKustomizeFilePath, - "resources:", allowWebhookTrafficFragment) + // Apply project-specific customizations: + // - Add reference to allow-webhook-traffic.yaml in network policy configuration. + // - Enable all webhook-related sections in config/default/kustomization.yaml. + addNetworkPoliciesForWebhooks() + // enableWebhookDefaults ensures all necessary components for webhook functionality + // are enabled in config/default/kustomization.yaml, including: + // - webhook and cert-manager directories + // - manager patches + // - replacements for certificate injection + enableWebhookDefaults() + if s.resource.HasValidationWebhook() { + uncommentCodeForValidationWebhooks() + } + if s.resource.HasDefaultingWebhook() { + uncommentCodeForDefaultWebhooks() + } + if s.resource.HasConversionWebhook() { + uncommentCodeForConversionWebhooks(s.resource) + } + + const helmPluginKey = "helm.kubebuilder.io/v1-alpha" + var helmPlugin interface{} + err := s.config.DecodePluginConfig(helmPluginKey, &helmPlugin) + if !errors.As(err, &config.PluginKeyNotFoundError{}) { + testChartPath := ".github/workflows/test-chart.yml" + //nolint:lll + _ = pluginutil.UncommentCode( + testChartPath, `# - name: Install cert-manager via Helm +# run: | +# helm repo add jetstack https://charts.jetstack.io +# helm repo update +# helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true +# +# - name: Wait for cert-manager to be ready +# run: | +# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager +# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector +# kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook +`, "#", + ) + + _ = pluginutil.ReplaceInFile(testChartPath, "# TODO: Uncomment if cert-manager is enabled", "") + } + + return nil +} + +// uncommentCodeForConversionWebhooks enables CA injection logic in Kustomize manifests +// for ConversionWebhooks by uncommenting certificate sources and CRD annotation targets. +// This is required to make cert-manager correctly inject the CA bundle into CRDs. +func uncommentCodeForConversionWebhooks(r resource.Resource) { + crdName := fmt.Sprintf("%s.%s", r.Plural, r.QualifiedGroup()) + err := pluginutil.UncommentCode( + kustomizeFilePath, + fmt.Sprintf(`# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# - select: +# kind: CustomResourceDefinition +# name: %s +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true`, crdName), + "#", + ) if err != nil { - log.Errorf("Unable to add the line '- allow-webhook-traffic.yaml' at the end of the file"+ - "%s to allow webhook traffic.", policyKustomizeFilePath) + log.Warningf("Unable to find the certificate namespace replacement for "+ + "CRD %s to uncomment in %s. Conversion webhooks require this replacement "+ + "to inject the CA properly.", + crdName, kustomizeFilePath) + } + err = pluginutil.UncommentCode( + kustomizeFilePath, + fmt.Sprintf(`# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# - select: +# kind: CustomResourceDefinition +# name: %s +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true`, crdName), + "#", + ) + if err != nil { + log.Warningf("Unable to find the certificate name replacement for CRD %s "+ + "to uncomment in %s. Conversion webhooks require this replacement to inject "+ + "the CA properly.", + crdName, kustomizeFilePath) } - kustomizeFilePath := "config/default/kustomization.yaml" - err = pluginutil.UncommentCode(kustomizeFilePath, "#- ../webhook", `#`) + err = pluginutil.UncommentCode(kustomizeCRDFilePath, `#configurations: +#- kustomizeconfig.yaml`, `#`) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeCRDFilePath, + `configurations: +- kustomizeconfig.yaml`) + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the target configurations with kustomizeconfig.yaml"+ + "to uncomment in the file %s. ConverstionWebhooks requires this configuration "+ + "to be uncommented to inject CA", kustomizeCRDFilePath) + } + } +} + +func uncommentCodeForDefaultWebhooks() { + err := pluginutil.UncommentCode( + kustomizeFilePath, + `# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# 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 +# fieldPath: .metadata.name +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true`, + "#", + ) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, + ` targets: + - select: + kind: MutatingWebhookConfiguration`) + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the MutatingWebhookConfiguration section "+ + "to uncomment in %s. Webhooks scaffolded with '--defaulting' require "+ + "this configuration for CA injection.", + kustomizeFilePath) + } + } +} + +func uncommentCodeForValidationWebhooks() { + err := pluginutil.UncommentCode( + kustomizeFilePath, + `# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) +# 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 +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true`, + "#", + ) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, + ` targets: + - select: + kind: ValidatingWebhookConfiguration`) + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the ValidatingWebhookConfiguration section "+ + "to uncomment in %s. Webhooks scaffolded with '--programmatic-validation' "+ + "require this configuration for CA injection.", + kustomizeFilePath) + } + } +} + +func enableWebhookDefaults() { + err := pluginutil.UncommentCode(kustomizeFilePath, "#- ../webhook", `#`) if err != nil { hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, "- ../webhook") if !hasWebHookUncommented || errCheck != nil { - log.Errorf("Unable to find the target #- ../webhook to uncomment in the file "+ + log.Warningf("Unable to find the target #- ../webhook to uncomment in the file "+ "%s.", kustomizeFilePath) } } @@ -127,7 +343,7 @@ func (s *webhookScaffolder) Scaffold() error { if err != nil { hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, "patches:") if !hasWebHookUncommented || errCheck != nil { - log.Errorf("Unable to find the line '#patches:' to uncomment in the file "+ + log.Warningf("Unable to find the line '#patches:' to uncomment in the file "+ "%s.", kustomizeFilePath) } } @@ -139,32 +355,103 @@ func (s *webhookScaffolder) Scaffold() error { hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, "- path: manager_webhook_patch.yaml") if !hasWebHookUncommented || errCheck != nil { - log.Errorf("Unable to find the target #- path: manager_webhook_patch.yaml to uncomment in the file "+ + log.Warningf("Unable to find the target #- path: manager_webhook_patch.yaml to uncomment in the file "+ "%s.", kustomizeFilePath) } } - if s.resource.Webhooks.Conversion { - crdKustomizationsFilePath := "config/crd/kustomization.yaml" - err = pluginutil.UncommentCode(crdKustomizationsFilePath, "#configurations:\n#- kustomizeconfig.yaml", `#`) - if err != nil { - hasWebHookUncommented, err := pluginutil.HasFileContentWith(crdKustomizationsFilePath, - "configurations:\n- kustomizeconfig.yaml") - if !hasWebHookUncommented || err != nil { - log.Warningf("Unable to find the target(s) configurations.kustomizeconfig.yaml "+ - "to uncomment in the file "+ - "%s.", crdKustomizationsFilePath) - } + err = pluginutil.UncommentCode(kustomizeFilePath, `#- ../certmanager`, `#`) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, + "../certmanager") + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the '../certmanager' section to uncomment in %s. "+ + "Projects that use webhooks must enable certificate management."+ + "Please ensure cert-manager integration is enabled.", + kustomizeFilePath) } } - return nil + err = pluginutil.UncommentCode(kustomizeFilePath, `#replacements:`, `#`) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, + "replacements:") + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the '#replacements:' section to uncomment in %s."+ + "Projects using webhooks must enable cert-manager CA injection by uncommenting"+ + "the required replacements.", + kustomizeFilePath) + } + } + + err = pluginutil.UncommentCode( + kustomizeFilePath, + `# - source: # Uncomment the following block if you have any webhook +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # Name of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# 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 +# name: serving-cert +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true`, + "#", + ) + if err != nil { + hasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, + ` kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name`) + if !hasWebHookUncommented || errCheck != nil { + log.Warningf("Unable to find the '#- source: # Uncomment the following block if you have any webhook' "+ + "section to uncomment in %s. "+ + "Projects with webhooks must enable certificates via cert-manager.", + kustomizeFilePath) + } + } +} + +func addNetworkPoliciesForWebhooks() { + policyKustomizeFilePath := "config/network-policy/kustomization.yaml" + err := pluginutil.InsertCodeIfNotExist(policyKustomizeFilePath, + "resources:", allowWebhookTrafficFragment) + if err != nil { + log.Errorf("Unable to add the line '- allow-webhook-traffic.yaml' at the end of the file"+ + "%s to allow webhook traffic.", policyKustomizeFilePath) + } } // Deprecated: remove it when go/v4 and/or kustomize/v2 be removed // validateScaffoldedProject will output a message to help users fix their scaffold func validateScaffoldedProject() { - kustomizeFilePath := "config/default/kustomization.yaml" hasCertManagerPatch, _ := pluginutil.HasFileContentWith(kustomizeFilePath, "crdkustomizecainjectionpatch") diff --git a/pkg/plugins/common/kustomize/v2/webhook.go b/pkg/plugins/common/kustomize/v2/webhook.go index 21e35177d77..81193a083af 100644 --- a/pkg/plugins/common/kustomize/v2/webhook.go +++ b/pkg/plugins/common/kustomize/v2/webhook.go @@ -17,6 +17,8 @@ limitations under the License. package v2 import ( + "fmt" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" "sigs.k8s.io/kubebuilder/v4/pkg/plugin" "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds" @@ -34,5 +36,9 @@ func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { } scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("failed to scaffold webhook subcommand: %w", err) + } + + return nil } diff --git a/pkg/plugins/external/external_test.go b/pkg/plugins/external/external_test.go index d4514095cf8..2b8e0cbdb47 100644 --- a/pkg/plugins/external/external_test.go +++ b/pkg/plugins/external/external_test.go @@ -84,7 +84,12 @@ func (m *mockValidFlagOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, e Universe: nil, Flags: getFlags(), } - return json.Marshal(response) + marshaledResponse, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("error marshalling response: %w", err) + } + + return marshaledResponse, nil } type mockValidMEOutputGetter struct{} @@ -97,7 +102,12 @@ func (m *mockValidMEOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, err Metadata: getMetadata(), } - return json.Marshal(response) + marshaledResponse, err := json.Marshal(response) + if err != nil { + return nil, fmt.Errorf("error marshalling response: %w", err) + } + + return marshaledResponse, nil } const ( @@ -287,10 +297,11 @@ var _ = Describe("Run external plugin using Scaffold", func() { flagset *pflag.FlagSet // Make an array of flags to represent the ones that should be returned in these tests - flags = getFlags() + flags []external.Flag checkFlagset func() ) + BeforeEach(func() { outputGetter = &mockValidFlagOutputGetter{} currentDirGetter = &mockValidOsWdGetter{} @@ -299,6 +310,8 @@ var _ = Describe("Run external plugin using Scaffold", func() { args = []string{"--captain", "black-beard", "--sail"} flagset = pflag.NewFlagSet("test", pflag.ContinueOnError) + flags = getFlags() + checkFlagset = func() { Expect(flagset.HasFlags()).To(BeTrue()) @@ -369,6 +382,7 @@ var _ = Describe("Run external plugin using Scaffold", func() { usage string checkFlagset func() ) + BeforeEach(func() { outputGetter = &mockInValidOutputGetter{} currentDirGetter = &mockValidOsWdGetter{} @@ -463,7 +477,15 @@ var _ = Describe("Run external plugin using Scaffold", func() { Context("Flag Parsing Helper Functions", func() { var ( - fs *pflag.FlagSet + fs *pflag.FlagSet + args []string + forbidden []string + flags []external.Flag + argFilters []argFilterFunc + externalFlagFilters []externalFlagFilterFunc + ) + + BeforeEach(func() { args = []string{ "--domain", "something.com", "--boolean", @@ -476,12 +498,7 @@ var _ = Describe("Run external plugin using Scaffold", func() { forbidden = []string{ "help", "group", "kind", "version", } - flags []external.Flag - argFilters []argFilterFunc - externalFlagFilters []externalFlagFilterFunc - ) - BeforeEach(func() { fs = pflag.NewFlagSet("test", pflag.ContinueOnError) flagsToAppend := getFlags() @@ -559,6 +576,7 @@ var _ = Describe("Run external plugin using Scaffold", func() { metadata *plugin.SubcommandMetadata checkMetadata func() ) + BeforeEach(func() { outputGetter = &mockValidMEOutputGetter{} currentDirGetter = &mockValidOsWdGetter{} @@ -623,6 +641,7 @@ var _ = Describe("Run external plugin using Scaffold", func() { metadata *plugin.SubcommandMetadata checkMetadata func() ) + BeforeEach(func() { outputGetter = &mockInValidOutputGetter{} currentDirGetter = &mockValidOsWdGetter{} diff --git a/pkg/plugins/external/helpers.go b/pkg/plugins/external/helpers.go index c3fc1bdb069..9c8a5f82f94 100644 --- a/pkg/plugins/external/helpers.go +++ b/pkg/plugins/external/helpers.go @@ -57,7 +57,7 @@ func (e *execOutputGetter) GetExecOutput(request []byte, path string) ([]byte, e cmd.Stderr = os.Stderr out, err := cmd.Output() if err != nil { - return nil, err + return nil, fmt.Errorf("error getting output for cmd %q: %w", cmd, err) } return out, nil @@ -75,7 +75,7 @@ type osWdGetter struct{} func (o *osWdGetter) GetCurrentDir() (string, error) { currentDir, err := os.Getwd() if err != nil { - return "", fmt.Errorf("error getting current directory: %v", err) + return "", fmt.Errorf("error getting current directory: %w", err) } return currentDir, nil @@ -84,17 +84,17 @@ func (o *osWdGetter) GetCurrentDir() (string, error) { func makePluginRequest(req external.PluginRequest, path string) (*external.PluginResponse, error) { reqBytes, err := json.Marshal(req) if err != nil { - return nil, err + return nil, fmt.Errorf("error marshalling plugin request: %w", err) } out, err := outputGetter.GetExecOutput(reqBytes, path) if err != nil { - return nil, err + return nil, fmt.Errorf("error executing plugin request: %w", err) } res := external.PluginResponse{} - if err := json.Unmarshal(out, &res); err != nil { - return nil, err + if err = json.Unmarshal(out, &res); err != nil { + return nil, fmt.Errorf("error unmarshalling plugin response: %w", err) } // Error if the plugin failed. @@ -114,7 +114,7 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { err := afero.Walk(fs.FS, ".", func(path string, info iofs.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("error walking path %q: %w", path, err) } if info.IsDir() { @@ -123,7 +123,7 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { file, err := fs.FS.Open(path) if err != nil { - return err + return fmt.Errorf("error opening file %q: %w", path, err) } defer func() { @@ -134,7 +134,7 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { content, err := io.ReadAll(file) if err != nil { - return err + return fmt.Errorf("error reading file %q: %w", path, err) } universe[path] = string(content) @@ -142,7 +142,7 @@ func getUniverseMap(fs machinery.Filesystem) (map[string]string, error) { return nil }) if err != nil { - return nil, err + return nil, fmt.Errorf("error walking the directory: %w", err) } return universe, nil @@ -153,7 +153,7 @@ func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, p req.Universe, err = getUniverseMap(fs) if err != nil { - return err + return fmt.Errorf("error getting universe map: %w", err) } res, err := makePluginRequest(req, path) @@ -163,31 +163,31 @@ func handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, p currentDir, err := currentDirGetter.GetCurrentDir() if err != nil { - return fmt.Errorf("error getting current directory: %v", err) + return fmt.Errorf("error getting current directory: %w", err) } for filename, data := range res.Universe { - path := filepath.Join(currentDir, filename) - dir := filepath.Dir(path) + file := filepath.Join(currentDir, filename) + dir := filepath.Dir(file) // create the directory if it does not exist - if err := os.MkdirAll(dir, 0o750); err != nil { - return fmt.Errorf("error creating the directory: %v", err) + if err = os.MkdirAll(dir, 0o750); err != nil { + return fmt.Errorf("error creating the directory: %w", err) } - f, err := fs.FS.Create(path) - if err != nil { - return err + f, createErr := fs.FS.Create(file) + if createErr != nil { + return fmt.Errorf("error creating file %q: %w", file, createErr) } defer func() { - if err := f.Close(); err != nil { + if err = f.Close(); err != nil { return } }() - if _, err := f.Write([]byte(data)); err != nil { - return err + if _, err = f.Write([]byte(data)); err != nil { + return fmt.Errorf("error writing file %q: %w", file, err) } } @@ -254,7 +254,7 @@ func bindSpecificFlags(fs *pflag.FlagSet, flags []external.Flag) { } func filterFlags(flags []external.Flag, externalFlagFilters []externalFlagFilterFunc) []external.Flag { - filteredFlags := []external.Flag{} + var filteredFlags []external.Flag for _, flag := range flags { ok := true for _, filter := range externalFlagFilters { @@ -271,7 +271,7 @@ func filterFlags(flags []external.Flag, externalFlagFilters []externalFlagFilter } func filterArgs(args []string, argFilters []argFilterFunc) []string { - filteredArgs := []string{} + var filteredArgs []string for _, arg := range args { ok := true for _, filter := range argFilters { @@ -318,7 +318,7 @@ var ( // helpArgFilter filters out any flag named "help" as its already bound helpArgFilter = func(arg string) bool { arg = strings.Replace(arg, "--", "", 1) - return !(arg == "help") + return arg != "help" } ) diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/api.go b/pkg/plugins/golang/deploy-image/v1alpha1/api.go index ff2b001a871..668cd1c81b7 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/api.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/api.go @@ -138,7 +138,7 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { - return err + return fmt.Errorf("error validating resource: %w", err) } // Check that the provided group can be added to the project @@ -186,17 +186,17 @@ func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder.InjectFS(fs) err := scaffolder.Scaffold() if err != nil { - return err + return fmt.Errorf("error scaffolding deploy-image plugin: %w", err) } // Track the resources following a declarative approach cfg := PluginConfig{} - if err := p.config.DecodePluginConfig(pluginKey, &cfg); errors.As(err, &config.UnsupportedFieldError{}) { + if err = p.config.DecodePluginConfig(pluginKey, &cfg); errors.As(err, &config.UnsupportedFieldError{}) { // Skip tracking as the config doesn't support per-plugin configuration return nil } else if err != nil && !errors.As(err, &config.PluginKeyNotFoundError{}) { // Fail unless the key wasn't found, which just means it is the first resource tracked - return err + return fmt.Errorf("error decoding plugin configuration: %w", err) } configDataOptions := options{ @@ -206,31 +206,36 @@ func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { RunAsUser: p.runAsUser, } cfg.Resources = append(cfg.Resources, ResourceData{ - Group: p.resource.GVK.Group, - Domain: p.resource.GVK.Domain, - Version: p.resource.GVK.Version, - Kind: p.resource.GVK.Kind, + Group: p.resource.Group, + Domain: p.resource.Domain, + Version: p.resource.Version, + Kind: p.resource.Kind, Options: configDataOptions, }) - return p.config.EncodePluginConfig(pluginKey, cfg) + + if err = p.config.EncodePluginConfig(pluginKey, cfg); err != nil { + return fmt.Errorf("error encoding plugin configuration: %w", err) + } + + return nil } func (p *createAPISubcommand) PostScaffold() error { err := util.RunCmd("Update dependencies", "go", "mod", "tidy") if err != nil { - return err + return fmt.Errorf("error updating go dependencies: %w", err) } if p.runMake && p.resource.HasAPI() { err = util.RunCmd("Running make", "make", "generate") if err != nil { - return err + return fmt.Errorf("ailed running make generate: %w", err) } } if p.runManifests && p.resource.HasAPI() { err = util.RunCmd("Running make", "make", "manifests") if err != nil { - return err + return fmt.Errorf("failed running make manifests: %w", err) } } diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go index c9fb02d7324..a7ca47fd05e 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go @@ -108,13 +108,13 @@ func (s *apiScaffolder) Scaffold() error { if err := scaffold.Execute( &api.Types{Port: s.port}, ); err != nil { - return fmt.Errorf("error updating APIs: %v", err) + return fmt.Errorf("error updating APIs: %w", err) } if err := scaffold.Execute( &samples.CRDSample{Port: s.port}, ); err != nil { - return fmt.Errorf("error updating config/samples: %v", err) + return fmt.Errorf("error updating config/samples: %w", err) } controller := &controllers.Controller{ @@ -124,22 +124,22 @@ func (s *apiScaffolder) Scaffold() error { if err := scaffold.Execute( controller, ); err != nil { - return fmt.Errorf("error scaffolding controller: %v", err) + return fmt.Errorf("error scaffolding controller: %w", err) } if err := s.updateControllerCode(*controller); err != nil { - return fmt.Errorf("error updating controller: %v", err) + return fmt.Errorf("error updating controller: %w", err) } defaultMainPath := "cmd/main.go" if err := s.updateMainByAddingEventRecorder(defaultMainPath); err != nil { - return fmt.Errorf("error updating main.go: %v", err) + return fmt.Errorf("error updating main.go: %w", err) } if err := scaffold.Execute( &controllers.ControllerTest{Port: s.port}, ); err != nil { - return fmt.Errorf("error creating controller/**_controller_test.go: %v", err) + return fmt.Errorf("error creating controller/**_controller_test.go: %w", err) } return s.addEnvVarIntoManager() @@ -169,11 +169,11 @@ func (s *apiScaffolder) addEnvVarIntoManager() error { // plugins to do the default scaffolds which an API is created func (s *apiScaffolder) scaffoldCreateAPI() error { if err := s.scaffoldCreateAPIFromGolang(); err != nil { - return fmt.Errorf("error scaffolding golang files for the new API: %v", err) + return fmt.Errorf("error scaffolding golang files for the new API: %w", err) } if err := s.scaffoldCreateAPIFromKustomize(); err != nil { - return fmt.Errorf("error scaffolding kustomize manifests for the new API: %v", err) + return fmt.Errorf("error scaffolding kustomize manifests for the new API: %w", err) } return nil } @@ -190,7 +190,7 @@ func (s *apiScaffolder) updateMainByAddingEventRecorder(defaultMainPath string) Scheme: mgr.GetScheme(),`, s.resource.Kind), fmt.Sprintf(recorderTemplate, strings.ToLower(s.resource.Kind)), ); err != nil { - return fmt.Errorf("error scaffolding event recorder in %s: %v", defaultMainPath, err) + return fmt.Errorf("error scaffolding event recorder in %q: %w", defaultMainPath, err) } return nil @@ -205,7 +205,7 @@ func (s *apiScaffolder) updateControllerCode(controller controllers.Controller) strings.ToLower(s.resource.Kind), // value for the name of the container ), ); err != nil { - return fmt.Errorf("error scaffolding container in the controller path (%s): %v", + return fmt.Errorf("error scaffolding container in the controller path %q: %w", controller.Path, err) } @@ -231,7 +231,7 @@ func (s *apiScaffolder) updateControllerCode(controller controllers.Controller) }, }, },`, fmt.Sprintf(commandTemplate, res)); err != nil { - return fmt.Errorf("error scaffolding command in the controller path (%s): %v", + return fmt.Errorf("error scaffolding command in the controller path %q: %w", controller.Path, err) } } @@ -254,7 +254,7 @@ func (s *apiScaffolder) updateControllerCode(controller controllers.Controller) strings.ToLower(s.resource.Kind), strings.ToLower(s.resource.Kind)), ); err != nil { - return fmt.Errorf("error scaffolding container port in the controller path (%s): %v", + return fmt.Errorf("error scaffolding container port in the controller path %q: %w", controller.Path, err) } @@ -266,7 +266,7 @@ func (s *apiScaffolder) updateControllerCode(controller controllers.Controller) `RunAsNonRoot: ptr.To(true),`, fmt.Sprintf(runAsUserTemplate, s.runAsUser), ); err != nil { - return fmt.Errorf("error scaffolding user-id in the controller path (%s): %v", + return fmt.Errorf("error scaffolding user-id in the controller path %q: %w", controller.Path, err) } } @@ -284,7 +284,7 @@ func (s *apiScaffolder) scaffoldCreateAPIFromKustomize() error { kustomizeScaffolder.InjectFS(s.fs) if err := kustomizeScaffolder.Scaffold(); err != nil { - return fmt.Errorf("error scaffolding kustomize files for the APIs: %v", err) + return fmt.Errorf("error scaffolding kustomize files for the APIs: %w", err) } return nil @@ -294,7 +294,11 @@ func (s *apiScaffolder) scaffoldCreateAPIFromGolang() error { golangV4Scaffolder := golangv4scaffolds.NewAPIScaffolder(s.config, s.resource, true) golangV4Scaffolder.InjectFS(s.fs) - return golangV4Scaffolder.Scaffold() + if err := golangV4Scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding golang files for the APIs: %v", err) + } + + return nil } const containerTemplate = `Containers: []corev1.Container{{ diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go index 532775086e9..ce5cb86488b 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go @@ -28,7 +28,7 @@ var _ machinery.Template = &Types{} // Types scaffolds the file that defines the schema for a CRD // -//nolint:maligned + type Types struct { machinery.TemplateMixin machinery.MultiGroupMixin @@ -75,33 +75,40 @@ import ( type {{ .Resource.Kind }}Spec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - - // Size defines the number of {{ .Resource.Kind }} 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"` + "`" + ` + + // size defines the number of {{ .Resource.Kind }} instances + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 + // +optional + Size *int32 ` + "`" + `json:"size,omitempty"` + "`" + ` {{ if not (isEmptyStr .Port) -}} - // Port defines the port that will be used to init the container with the image - ContainerPort int32 ` + "`" + `json:"containerPort,omitempty"` + "`" + ` + // containerPort defines the port that will be used to init the container with the image + // +required + ContainerPort int32 ` + "`" + `json:"containerPort"` + "`" + ` {{- end }} } // {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }} type {{ .Resource.Kind }}Status struct { - // Represents the observations of a {{ .Resource.Kind }}'s current state. - // {{ .Resource.Kind }}.status.conditions.type are: "Available", "Progressing", and "Degraded" - // {{ .Resource.Kind }}.status.conditions.status are one of True, False, Unknown. - // {{ .Resource.Kind }}.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. - // {{ .Resource.Kind }}.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"` + "`" + ` + // For Kubernetes API conventions, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + // conditions represent the current state of the {{ .Resource.Kind }} resource. + // Each condition has a unique type and reflects the status of a specific aspect of the resource. + // + // Standard condition types include: + // - "Available": the resource is fully functional + // - "Progressing": the resource is being created or updated + // - "Degraded": the resource failed to reach or maintain its desired state + // + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition ` + "`" + `json:"conditions,omitempty"` + "`" + ` } // +kubebuilder:object:root=true @@ -117,10 +124,18 @@ type {{ .Resource.Kind }}Status struct { // {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` - metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` - Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` - Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty,omitzero"` + "`" + ` + + // spec defines the desired state of {{ .Resource.Kind }} + // +required + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec"` + "`" + ` + + // status defines the observed state of {{ .Resource.Kind }} + // +optional + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty,omitzero"` + "`" + ` } // +kubebuilder:object:root=true diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go index 421554a12ad..498c10c36b8 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go @@ -28,7 +28,7 @@ var _ machinery.Template = &ControllerTest{} // ControllerTest scaffolds the file that defines tests for the controller for a CRD or a builtin resource // -//nolint:maligned + type ControllerTest struct { machinery.TemplateMixin machinery.MultiGroupMixin @@ -77,6 +77,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" {{ if not (isEmptyStr .Resource.Path) -}} @@ -121,13 +122,13 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() { 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 - {{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{ + {{ lower .Resource.Kind }} = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{ ObjectMeta: metav1.ObjectMeta{ Name: {{ .Resource.Kind }}Name, Namespace: namespace.Name, }, Spec: {{ .Resource.ImportAlias }}.{{ .Resource.Kind }}Spec{ - Size: 1, + Size: ptr.To(int32(1)), {{ if not (isEmptyStr .Port) -}} ContainerPort: {{ .Port }}, {{- end }} @@ -191,7 +192,7 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() { By("Checking the latest Status Condition added to the {{ .Resource.Kind }} instance") Expect(k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})).To(Succeed()) - conditions := []metav1.Condition{} + var conditions []metav1.Condition Expect({{ lower .Resource.Kind }}.Status.Conditions).To(ContainElement( HaveField("Type", Equal(typeAvailable{{ .Resource.Kind }})), &conditions)) Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailable{{ .Resource.Kind }}) 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 4f2e14856e7..5b59f9d54d2 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 @@ -28,7 +28,7 @@ var _ machinery.Template = &Controller{} // Controller scaffolds the file that defines the controller for a CRD or a builtin resource // -//nolint:maligned + type Controller struct { machinery.TemplateMixin machinery.MultiGroupMixin @@ -153,8 +153,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl log.Error(err, "Failed to get {{ lower .Resource.Kind }}") return ctrl.Result{}, err } - - // Let's just set the status as Unknown when no status is available + if 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 { @@ -178,12 +177,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl // 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 }}") - if ok := controllerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok { - err = fmt.Errorf("finalizer for {{ .Resource.Kind }} was not added") - log.Error(err, "Failed to add finalizer for {{ .Resource.Kind }}") - return ctrl.Result{}, err - } - + controllerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) if err = r.Update(ctx, {{ lower .Resource.Kind }}); err != nil { log.Error(err, "Failed to update custom resource to add finalizer") return ctrl.Result{}, err @@ -288,13 +282,18 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } + // If the size is not defined in the Custom Resource then we will set the desired replicas to 0 + var desiredReplicas int32 = 0 + if {{ lower .Resource.Kind }}.Spec.Size != nil { + desiredReplicas = *{{ lower .Resource.Kind }}.Spec.Size + } + // 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 found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas { + found.Spec.Replicas = ptr.To(desiredReplicas) if err = r.Update(ctx, found); err != nil { log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) @@ -330,7 +329,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl // The following implementation will update the status meta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }}, Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", {{ lower .Resource.Kind }}.Name, size)}) + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", {{ lower .Resource.Kind }}.Name, desiredReplicas)}) if err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil { log.Error(err, "Failed to update {{ .Resource.Kind }} status") @@ -364,7 +363,6 @@ func (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Ki func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}( {{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (*appsv1.Deployment, error) { ls := labelsFor{{ .Resource.Kind }}() - replicas := {{ lower .Resource.Kind }}.Spec.Size // Get the Operand image image, err := imageFor{{ .Resource.Kind }}() @@ -378,7 +376,7 @@ func (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}( Namespace: {{ lower .Resource.Kind }}.Namespace, }, Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, + Replicas: {{ lower .Resource.Kind }}.Spec.Size, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -459,7 +457,7 @@ func imageFor{{ .Resource.Kind }}() (string, error) { var imageEnvVar = "{{ upper .Resource.Kind }}_IMAGE" image, found := os.LookupEnv(imageEnvVar) if !found { - return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + return "", fmt.Errorf("unable to find %s environment variable with the image", imageEnvVar) } return image, nil } diff --git a/pkg/plugins/golang/go_version.go b/pkg/plugins/golang/go_version.go index a045d078a66..d1118b844fb 100644 --- a/pkg/plugins/golang/go_version.go +++ b/pkg/plugins/golang/go_version.go @@ -64,18 +64,18 @@ func (v *GoVersion) parse(verStr string) error { v.major, err = strconv.Atoi(m[1]) if err != nil { - return fmt.Errorf("error parsing major version '%s': %s", m[1], err) + return fmt.Errorf("error parsing major version %q: %w", m[1], err) } v.minor, err = strconv.Atoi(m[2]) if err != nil { - return fmt.Errorf("error parsing minor version '%s': %s", m[2], err) + return fmt.Errorf("error parsing minor version %q: %w", m[2], err) } if m[3] != "" { v.patch, err = strconv.Atoi(m[3]) if err != nil { - return fmt.Errorf("error parsing patch version '%s': %s", m[2], err) + return fmt.Errorf("error parsing patch version %q: %w", m[2], err) } } @@ -126,7 +126,7 @@ func (v GoVersion) Compare(other GoVersion) int { func ValidateGoVersion(minVersion, maxVersion GoVersion) error { err := fetchAndCheckGoVersion(minVersion, maxVersion) if err != nil { - return fmt.Errorf("%s. You can skip this check using the --skip-go-version-check flag", err) + return fmt.Errorf("you can skip this check using the --skip-go-version-check flag: %w", err) } return nil } @@ -144,7 +144,7 @@ func fetchAndCheckGoVersion(minVersion, maxVersion GoVersion) error { } goVer := split[2] if err := checkGoVersion(goVer, minVersion, maxVersion); err != nil { - return fmt.Errorf("go version '%s' is incompatible because '%s'", goVer, err) + return fmt.Errorf("go version %q is incompatible: %w", goVer, err) } return nil } @@ -156,7 +156,7 @@ func checkGoVersion(verStr string, minVersion, maxVersion GoVersion) error { } if version.Compare(minVersion) < 0 || version.Compare(maxVersion) >= 0 { - return fmt.Errorf("plugin requires %s <= version < %s", minVersion, maxVersion) + return fmt.Errorf("plugin requires %q <= version < %q", minVersion, maxVersion) } return nil diff --git a/pkg/plugins/golang/go_version_test.go b/pkg/plugins/golang/go_version_test.go index 63016a6fa9c..f19d8304bb6 100644 --- a/pkg/plugins/golang/go_version_test.go +++ b/pkg/plugins/golang/go_version_test.go @@ -80,6 +80,11 @@ var _ = Describe("GoVersion", func() { Context("Compare", func() { // Test Compare() by sorting a list. var ( + versions []GoVersion + sortedVersions []GoVersion + ) + + BeforeEach(func() { versions = []GoVersion{ {major: 1, minor: 15, prerelease: "rc2"}, {major: 1, minor: 15, patch: 1}, @@ -113,7 +118,7 @@ var _ = Describe("GoVersion", func() { {major: 1, minor: 16}, {major: 2, minor: 0}, } - ) + }) It("sorts a valid list of versions correctly", func() { sort.Slice(versions, func(i int, j int) bool { @@ -125,8 +130,15 @@ var _ = Describe("GoVersion", func() { }) var _ = Describe("checkGoVersion", func() { - goVerMin := MustParse("go1.13") - goVerMax := MustParse("go2.0alpha1") + var ( + goVerMin GoVersion + goVerMax GoVersion + ) + + BeforeEach(func() { + goVerMin = MustParse("go1.13") + goVerMax = MustParse("go2.0alpha1") + }) DescribeTable("should return no error for supported go versions", func(version string) { Expect(checkGoVersion(version, goVerMin, goVerMax)).To(Succeed()) }, @@ -201,6 +213,7 @@ var _ = Describe("checkGoVersion", func() { Entry("for go.1.21", "go1.21"), Entry("for go.1.22", "go1.22"), Entry("for go.1.23", "go1.23"), + Entry("for go.1.24", "go1.24"), ) DescribeTable("should return an error for non-supported go versions", diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index 04a17242bd9..6c8b0c8b914 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -88,7 +88,6 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { CRDVersion: "v1", Namespaced: opts.Namespaced, } - } if opts.DoController { diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 13e4f624255..8d7c5ad34bf 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -35,7 +35,13 @@ var _ = Describe("Options", func() { version = "v1" kind = "FirstMate" ) + var ( + gvk resource.GVK + cfg config.Config + ) + + BeforeEach(func() { gvk = resource.GVK{ Group: group, Domain: domain, @@ -43,10 +49,6 @@ var _ = Describe("Options", func() { Kind: kind, } - cfg config.Config - ) - - BeforeEach(func() { cfg = cfgv3.New() _ = cfg.SetRepository("test") }) diff --git a/pkg/plugins/golang/repository.go b/pkg/plugins/golang/repository.go index 47debda41c3..e815e5dce4c 100644 --- a/pkg/plugins/golang/repository.go +++ b/pkg/plugins/golang/repository.go @@ -18,6 +18,7 @@ package golang import ( "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -39,14 +40,15 @@ func findGoModulePath() (string, error) { cmd.Env = append(cmd.Env, os.Environ()...) out, err := cmd.Output() if err != nil { - if exitErr, isExitErr := err.(*exec.ExitError); isExitErr { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { err = fmt.Errorf("%s", string(exitErr.Stderr)) } return "", err } mod := goMod{} - if err := json.Unmarshal(out, &mod); err != nil { - return "", err + if err = json.Unmarshal(out, &mod); err != nil { + return "", fmt.Errorf("failed to unmarshal go.mod: %w", err) } return mod.Module.Path, nil } @@ -77,12 +79,13 @@ func FindCurrentRepo() (string, error) { cmd := exec.Command("go", "mod", "init") cmd.Env = append(cmd.Env, os.Environ()...) if _, err := cmd.Output(); err != nil { - if exitErr, isExitErr := err.(*exec.ExitError); isExitErr { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { err = fmt.Errorf("%s", string(exitErr.Stderr)) } // give up, let the user figure it out return "", fmt.Errorf("could not determine repository path from module data, "+ - "package data, or by initializing a module: %v", err) + "package data, or by initializing a module: %w", err) } //nolint:errcheck defer os.Remove("go.mod") // clean up after ourselves diff --git a/pkg/plugins/golang/v4/api.go b/pkg/plugins/golang/v4/api.go index ac690f46a31..cc2b4cc6f85 100644 --- a/pkg/plugins/golang/v4/api.go +++ b/pkg/plugins/golang/v4/api.go @@ -139,16 +139,16 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { // Ensure that external API options cannot be used when creating an API in the project. if p.options.DoAPI { if len(p.options.ExternalAPIPath) != 0 || len(p.options.ExternalAPIDomain) != 0 { - return errors.New("Cannot use '--external-api-path' or '--external-api-domain' " + + return errors.New("cannot use '--external-api-path' or '--external-api-domain' " + "when creating an API in the project with '--resource=true'. " + - "Use '--resource=false' when referencing an external API.") + "Use '--resource=false' when referencing an external API") } } p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { - return err + return fmt.Errorf("error validating resource: %w", err) } // In case we want to scaffold a resource API we need to do some checks @@ -180,18 +180,22 @@ func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error { func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding API: %w", err) + } + + return nil } func (p *createAPISubcommand) PostScaffold() error { err := util.RunCmd("Update dependencies", "go", "mod", "tidy") if err != nil { - return err + return fmt.Errorf("error updating go dependencies: %w", err) } if p.runMake && p.resource.HasAPI() { err = util.RunCmd("Running make", "make", "generate") if err != nil { - return err + return fmt.Errorf("error running make generate: %w", err) } fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n") } diff --git a/pkg/plugins/golang/v4/edit.go b/pkg/plugins/golang/v4/edit.go index a53c12b1610..d60567bf150 100644 --- a/pkg/plugins/golang/v4/edit.go +++ b/pkg/plugins/golang/v4/edit.go @@ -61,5 +61,9 @@ func (p *editSubcommand) InjectConfig(c config.Config) error { func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("failed to edit scaffold: %w", err) + } + + return nil } diff --git a/pkg/plugins/golang/v4/init.go b/pkg/plugins/golang/v4/init.go index acae1da1352..c07024b642b 100644 --- a/pkg/plugins/golang/v4/init.go +++ b/pkg/plugins/golang/v4/init.go @@ -101,32 +101,35 @@ func (p *initSubcommand) InjectConfig(c config.Config) error { if p.repo == "" { repoPath, err := golang.FindCurrentRepo() if err != nil { - return fmt.Errorf("error finding current repository: %v", err) + return fmt.Errorf("error finding current repository: %w", err) } p.repo = repoPath } - return p.config.SetRepository(p.repo) + if err := p.config.SetRepository(p.repo); err != nil { + return fmt.Errorf("error setting repository: %w", err) + } + + return nil } func (p *initSubcommand) PreScaffold(machinery.Filesystem) error { // Ensure Go version is in the allowed range if check not turned off. if !p.skipGoVersionCheck { if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil { - return err + return fmt.Errorf("error validating go version: %w", err) } } - // Check if the current directory has not files or directories which does not allow to init the project + // Check if the current directory has no files or directories which does not allow to init the project return checkDir() } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner, p.commandName) scaffolder.InjectFS(fs) - err := scaffolder.Scaffold() - if err != nil { - return err + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding init plugin: %w", err) } if !p.fetchDeps { @@ -136,10 +139,10 @@ func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { // Ensure that we are pinning controller-runtime version // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997 - err = util.RunCmd("Get controller runtime", "go", "get", + err := util.RunCmd("Get controller runtime", "go", "get", "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion) if err != nil { - return err + return fmt.Errorf("error getting controller-runtime version: %w", err) } return nil @@ -148,7 +151,7 @@ func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { func (p *initSubcommand) PostScaffold() error { err := util.RunCmd("Update dependencies", "go", "mod", "tidy") if err != nil { - return err + return fmt.Errorf("error updating go dependencies: %w", err) } fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) @@ -162,7 +165,7 @@ func checkDir() error { err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("error walking path %q: %w", path, err) } // Allow directory trees starting with '.' if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { @@ -200,13 +203,12 @@ func checkDir() error { } } // Do not allow any other file - return fmt.Errorf( - "target directory is not empty and contains a disallowed file %q. "+ - "files with the following extensions [%s] are not allowed to avoid conflicts with the tooling.", + return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+ + "files with the following extensions [%s] are not allowed to avoid conflicts with the tooling", path, strings.Join(disallowedExtensions, ", ")) }) if err != nil { - return err + return fmt.Errorf("error walking directory: %w", err) } return nil } diff --git a/pkg/plugins/golang/v4/scaffolds/api.go b/pkg/plugins/golang/v4/scaffolds/api.go index eacf5af18d3..19bc06dca87 100644 --- a/pkg/plugins/golang/v4/scaffolds/api.go +++ b/pkg/plugins/golang/v4/scaffolds/api.go @@ -49,9 +49,9 @@ type apiScaffolder struct { } // NewAPIScaffolder returns a new Scaffolder for API/controller creation operations -func NewAPIScaffolder(config config.Config, res resource.Resource, force bool) plugins.Scaffolder { +func NewAPIScaffolder(cfg config.Config, res resource.Resource, force bool) plugins.Scaffolder { return &apiScaffolder{ - config: config, + config: cfg, resource: res, force: force, } @@ -101,7 +101,7 @@ func (s *apiScaffolder) Scaffold() error { &api.Types{Force: s.force}, &api.Group{}, ); err != nil { - return fmt.Errorf("error scaffolding APIs: %v", err) + return fmt.Errorf("error scaffolding APIs: %w", err) } } @@ -111,14 +111,14 @@ func (s *apiScaffolder) Scaffold() error { &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, &controllers.ControllerTest{Force: s.force, DoAPI: doAPI}, ); err != nil { - return fmt.Errorf("error scaffolding controller: %v", err) + return fmt.Errorf("error scaffolding controller: %w", err) } } if err := scaffold.Execute( &cmd.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { - return fmt.Errorf("error updating cmd/main.go: %v", err) + return fmt.Errorf("error updating cmd/main.go: %w", err) } return nil diff --git a/pkg/plugins/golang/v4/scaffolds/edit.go b/pkg/plugins/golang/v4/scaffolds/edit.go index eb5b8a7e505..fcc4b791194 100644 --- a/pkg/plugins/golang/v4/scaffolds/edit.go +++ b/pkg/plugins/golang/v4/scaffolds/edit.go @@ -17,6 +17,8 @@ limitations under the License. package scaffolds import ( + "fmt" + "github.com/spf13/afero" "sigs.k8s.io/kubebuilder/v4/pkg/config" @@ -35,9 +37,9 @@ type editScaffolder struct { } // NewEditScaffolder returns a new Scaffolder for configuration edit operations -func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder { +func NewEditScaffolder(cfg config.Config, multigroup bool) plugins.Scaffolder { return &editScaffolder{ - config: config, + config: cfg, multigroup: multigroup, } } @@ -52,7 +54,7 @@ func (s *editScaffolder) Scaffold() error { filename := "Dockerfile" bs, err := afero.ReadFile(s.fs.FS, filename) if err != nil { - return err + return fmt.Errorf("error reading %q: %w", filename, err) } str := string(bs) @@ -66,7 +68,9 @@ func (s *editScaffolder) Scaffold() error { // because there is nothing to replace. if str != "" { // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency - return afero.WriteFile(s.fs.FS, filename, []byte(str), 0o644) + if err = afero.WriteFile(s.fs.FS, filename, []byte(str), 0o644); err != nil { + return fmt.Errorf("error writing %q: %w", filename, err) + } } return nil diff --git a/pkg/plugins/golang/v4/scaffolds/init.go b/pkg/plugins/golang/v4/scaffolds/init.go index feb428ad5f1..4e291c1e94c 100644 --- a/pkg/plugins/golang/v4/scaffolds/init.go +++ b/pkg/plugins/golang/v4/scaffolds/init.go @@ -38,11 +38,11 @@ import ( const ( // GolangciLintVersion is the golangci-lint version to be used in the project - GolangciLintVersion = "v1.63.4" + GolangciLintVersion = "v2.1.6" // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project - ControllerRuntimeVersion = "v0.20.4" + ControllerRuntimeVersion = "v0.21.0" // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project - ControllerToolsVersion = "v0.17.2" + ControllerToolsVersion = "v0.18.0" imageName = "controller:latest" ) @@ -63,9 +63,9 @@ type initScaffolder struct { } // NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config config.Config, license, owner, commandName string) plugins.Scaffolder { +func NewInitScaffolder(cfg config.Config, license, owner, commandName string) plugins.Scaffolder { return &initScaffolder{ - config: config, + config: cfg, boilerplatePath: hack.DefaultBoilerplatePath, license: license, owner: owner, @@ -110,7 +110,7 @@ func (s *initScaffolder) Scaffold() error { } bpFile.Path = s.boilerplatePath if err := scaffold.Execute(bpFile); err != nil { - return err + return fmt.Errorf("failed to execute boilerplate: %w", err) } boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath) @@ -152,7 +152,7 @@ func (s *initScaffolder) Scaffold() error { } } - return scaffold.Execute( + err := scaffold.Execute( &cmd.Main{ ControllerRuntimeVersion: ControllerRuntimeVersion, }, @@ -185,4 +185,9 @@ func (s *initScaffolder) Scaffold() error { &templates.DevContainer{}, &templates.DevContainerPostInstallScript{}, ) + if err != nil { + return fmt.Errorf("failed to execute init scaffold: %w", err) + } + + return nil } diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go index 01458b41ba4..f64f9ab6e71 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go @@ -28,7 +28,7 @@ var _ machinery.Template = &Hub{} // Hub scaffolds the file that defines hub // -//nolint:maligned + type Hub struct { machinery.TemplateMixin machinery.MultiGroupMixin diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go index 62d3daa5c9e..89a11a47bb7 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go @@ -79,8 +79,14 @@ func (src *{{ .Resource.Kind }}) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) log.Printf("ConvertTo: Converting {{ .Resource.Kind }} from Spoke version {{ .SpokeVersion }} to Hub version {{ .Resource.Version }};" + "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) - + // TODO(user): Implement conversion logic from {{ .SpokeVersion }} to {{ .Resource.Version }} + // Example: Copying Spec fields + // dst.Spec.Size = src.Spec.Replicas + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } @@ -91,6 +97,12 @@ func (dst *{{ .Resource.Kind }}) ConvertFrom(srcRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from {{ .Resource.Version }} to {{ .SpokeVersion }} + // Example: Copying Spec fields + // dst.Spec.Replicas = src.Spec.Size + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } ` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go index 7c0d506af6b..e4fdc682d7d 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go @@ -28,7 +28,7 @@ var _ machinery.Template = &Types{} // Types scaffolds the file that defines the schema for a CRD // -//nolint:maligned + type Types struct { machinery.TemplateMixin machinery.MultiGroupMixin @@ -73,13 +73,16 @@ import ( // 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. -// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}. +// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }} type {{ .Resource.Kind }}Spec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update - Foo string ` + "`" + `json:"foo,omitempty"` + "`" + ` + // foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update + // +optional + Foo *string ` + "`" + `json:"foo,omitempty"` + "`" + ` } // {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}. @@ -98,18 +101,26 @@ type {{ .Resource.Kind }}Status struct { // +kubebuilder:resource:path={{ .Resource.Plural }} {{- end }} -// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API. +// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` - metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` - Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` - Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty,omitzero"` + "`" + ` + + // spec defines the desired state of {{ .Resource.Kind }} + // +required + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec"` + "`" + ` + + // status defines the observed state of {{ .Resource.Kind }} + // +optional + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty,omitzero"` + "`" + ` } // +kubebuilder:object:root=true -// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}. +// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }} type {{ .Resource.Kind }}List struct { metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go index c455ed663f4..52cf3886a8f 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go @@ -55,7 +55,7 @@ func (f *Main) SetTemplateDefaults() error { var _ machinery.Inserter = &MainUpdater{} // MainUpdater updates cmd/main.go to run Controllers -type MainUpdater struct { //nolint:maligned +type MainUpdater struct { machinery.RepositoryMixin machinery.MultiGroupMixin machinery.ResourceMixin @@ -108,7 +108,7 @@ const ( ` addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme)) ` - reconcilerSetupCodeFragment = `if err = (&controller.%sReconciler{ + reconcilerSetupCodeFragment = `if err := (&controller.%sReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -116,7 +116,7 @@ const ( os.Exit(1) } ` - multiGroupReconcilerSetupCodeFragment = `if err = (&%scontroller.%sReconciler{ + multiGroupReconcilerSetupCodeFragment = `if err := (&%scontroller.%sReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -126,7 +126,7 @@ const ( ` webhookSetupCodeFragmentLegacy = `// nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { + if err := (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "%s") os.Exit(1) } @@ -135,7 +135,7 @@ const ( webhookSetupCodeFragment = `// nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = %s.Setup%sWebhookWithManager(mgr); err != nil { + if err := %s.Setup%sWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "%s") os.Exit(1) } @@ -158,10 +158,11 @@ func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } if f.WireWebhook && !f.IsLegacyPath { - importPath := fmt.Sprintf("webhook%s", f.Resource.ImportAlias()) if !f.MultiGroup || f.Resource.Group == "" { + importPath := fmt.Sprintf("webhook%s", f.Resource.Version) imports = append(imports, fmt.Sprintf(webhookImportCodeFragment, importPath, f.Repo, f.Resource.Version)) } else { + importPath := fmt.Sprintf("webhook%s", f.Resource.ImportAlias()) imports = append(imports, fmt.Sprintf(multiGroupWebhookImportCodeFragment, importPath, f.Repo, f.Resource.Group, f.Resource.Version)) } @@ -198,8 +199,13 @@ func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { setup = append(setup, fmt.Sprintf(webhookSetupCodeFragmentLegacy, f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) } else { - setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, - "webhook"+f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + if !f.MultiGroup || f.Resource.Group == "" { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + "webhook"+f.Resource.Version, f.Resource.Kind, f.Resource.Kind)) + } else { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + "webhook"+f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + } } } @@ -277,7 +283,7 @@ func main() { flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") - flag.StringVar(&metricsCertPath, "metrics-cert-path", "", + flag.StringVar(&metricsCertPath, "metrics-cert-path", "", "The directory that contains the metrics server certificate.") flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") @@ -315,7 +321,7 @@ func main() { if len(webhookCertPath) > 0 { setupLog.Info("Initializing webhook certificate watcher using provided certificates", "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) - + var err error webhookCertWatcher, err = certwatcher.New( filepath.Join(webhookCertPath, webhookCertName), @@ -325,7 +331,7 @@ func main() { setupLog.Error(err, "Failed to initialize webhook certificate watcher") os.Exit(1) } - + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { config.GetCertificate = webhookCertWatcher.GetCertificate }) diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go index 72d348a07da..f18142dc13e 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go @@ -28,7 +28,7 @@ var _ machinery.Template = &Controller{} // Controller scaffolds the file that defines the controller for a CRD or a builtin resource // -//nolint:maligned + type Controller struct { machinery.TemplateMixin machinery.MultiGroupMixin diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go index 9bc9b4b178a..164ff214cc8 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -32,7 +32,7 @@ var ( // SuiteTest scaffolds the file that sets up the controller tests // -//nolint:maligned + type SuiteTest struct { machinery.TemplateMixin machinery.MultiGroupMixin diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go index ce7b46abf2e..20c26c240c3 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go @@ -28,7 +28,7 @@ var _ machinery.Template = &ControllerTest{} // ControllerTest scaffolds the file that sets up the controller unit tests // -//nolint:maligned + type ControllerTest struct { machinery.TemplateMixin machinery.MultiGroupMixin diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go index 25da1f4287a..3cb75392d17 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go @@ -22,7 +22,7 @@ import ( const devContainerTemplate = `{ "name": "Kubebuilder DevContainer", - "image": "docker.io/golang:1.23", + "image": "golang:1.24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go index 6118b68e839..5f98c67351b 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go @@ -39,7 +39,7 @@ func (f *Dockerfile) SetTemplateDefaults() error { } const dockerfileTemplate = `# Build the manager binary -FROM docker.io/golang:1.23 AS builder +FROM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go index 5214e9226a1..730eba48ab4 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go @@ -66,7 +66,7 @@ jobs: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: version: {{ .GolangciLintVersion }} ` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go index e5620ec29f0..cf1c4bf910f 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go @@ -28,6 +28,7 @@ var _ machinery.Template = &E2eTestCi{} type E2eTestCi struct { machinery.TemplateMixin machinery.BoilerplateMixin + machinery.ProjectNameMixin } // SetTemplateDefaults implements machinery.Template @@ -71,9 +72,6 @@ jobs: - name: Verify kind installation run: kind version - - name: Create kind cluster - run: kind create cluster - - name: Running Test e2e run: | go mod tidy diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go index 6fa9741980a..d2a1d1fc012 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go @@ -41,36 +41,18 @@ func (f *Golangci) SetTemplateDefaults() error { return nil } -const golangciTemplate = `run: - timeout: 5m +const golangciTemplate = `version: "2" +run: 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 + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -79,13 +61,36 @@ linters: - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ ` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go index ba8c75d3a71..d4c9e79fb65 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go @@ -45,9 +45,7 @@ func (f *GoMod) SetTemplateDefaults() error { const goModTemplate = `module {{ .Repo }} -go 1.23.0 - -godebug default=go1.23 +go 1.24.0 require ( sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }} diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go index 852ad765767..b200fca0f90 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go @@ -48,9 +48,9 @@ type Boilerplate struct { } // Validate implements file.RequiresValidation -func (f Boilerplate) Validate() error { +func (f *Boilerplate) Validate() error { if f.License != "" { - if _, found := knownLicenses[f.License]; !found { + if _, foundKnown := knownLicenses[f.License]; !foundKnown { if _, found := f.Licenses[f.License]; !found { return fmt.Errorf("unknown specified license %s", f.License) } diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go index bb47e9eece0..419ba8fd662 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go @@ -144,17 +144,30 @@ test: manifests generate fmt vet setup-envtest ## Run tests. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # CertManager is installed by default; skip with: # - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +KIND_CLUSTER ?= {{ .ProjectName }}-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist @command -v $(KIND) >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ } - @$(KIND) get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -285,7 +298,7 @@ $(ENVTEST): $(LOCALBIN) .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)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/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 diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go index 1183bcec82e..078e3cb088b 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go @@ -73,7 +73,7 @@ const readmeFileTemplate = `# {{ .ProjectName }} ## Getting Started ### Prerequisites -- go version v1.23.0+ +- go version v1.24.0+ - docker version 17.03+. - kubectl version v1.11.3+. - Access to a Kubernetes v1.11.3+ cluster. diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go index 063107b7b32..47aa977f599 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go @@ -71,7 +71,7 @@ var ( ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. +// temporary environment to validate project changes with the purpose of being used in CI jobs. // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs // CertManager. func TestE2E(t *testing.T) { diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go index 7cb67bcbbb8..18a7dec17e0 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go @@ -384,6 +384,7 @@ var _ = Describe("Manager", Ordered, func() { "command": ["/bin/sh", "-c"], "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], "securityContext": { + "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] @@ -395,7 +396,7 @@ var _ = Describe("Manager", Ordered, func() { } } }], - "serviceAccount": "%s" + "serviceAccountName": "%s" } }` + "`" + `, token, metricsServiceName, namespace, serviceAccountName)) _, err = utils.Run(cmd) diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go index 5ea882f0908..499ebbfec09 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go @@ -26,6 +26,7 @@ var _ machinery.Template = &Utils{} type Utils struct { machinery.TemplateMixin machinery.BoilerplateMixin + machinery.ProjectNameMixin } // SetTemplateDefaults set the defaults for its template @@ -51,7 +52,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck ) const ( @@ -73,15 +74,15 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) output, err := cmd.CombinedOutput() if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return string(output), nil @@ -222,13 +223,12 @@ func GetNonEmptyLines(output string) []string { func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { - return wd, err + return wd, fmt.Errorf("failed to get current working directory: %w", err) } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } - // UncommentCode searches for target in the file and remove the comment prefix // of the target content. The target content may span multiple lines. func UncommentCode(filename, target, prefix string) error { @@ -236,19 +236,19 @@ func UncommentCode(filename, target, prefix string) error { // nolint:gosec content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to output: %w", err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -256,25 +256,28 @@ func UncommentCode(filename, target, prefix string) error { return nil } for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } - if _, err := out.WriteString("\n"); err != nil { - return err + if _, err = out.WriteString("\n"); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } + // false positive // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } ` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go index 5f71f400fa3..40b5eee2b2f 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go @@ -28,7 +28,7 @@ import ( var _ machinery.Template = &Webhook{} // Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource -type Webhook struct { //nolint:maligned +type Webhook struct { machinery.TemplateMixin machinery.MultiGroupMixin machinery.BoilerplateMixin @@ -53,7 +53,6 @@ type Webhook struct { //nolint:maligned func (f *Webhook) SetTemplateDefaults() error { if f.Path == "" { // Deprecated: Remove me when remove go/v4 - //nolint:goconst baseDir := "api" if !f.IsLegacyPath { baseDir = filepath.Join("internal", "webhook") @@ -85,7 +84,7 @@ func (f *Webhook) SetTemplateDefaults() error { } f.AdmissionReviewVersions = "v1" - f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) + f.QualifiedGroupWithDash = strings.ReplaceAll(f.Resource.QualifiedGroup(), ".", "-") return nil } @@ -175,7 +174,7 @@ type {{ .Resource.Kind }}CustomDefaulter struct { var _ webhook.CustomDefaulter = &{{ .Resource.Kind }}CustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind {{ .Resource.Kind }}. -func (d *{{ .Resource.Kind }}CustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *{{ .Resource.Kind }}CustomDefaulter) Default(_ context.Context, obj runtime.Object) error { {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }}) {{- else }} @@ -215,7 +214,7 @@ type {{ .Resource.Kind }}CustomValidator struct{ var _ webhook.CustomValidator = &{{ .Resource.Kind }}CustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}. -func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := obj.(*{{ .Resource.Kind }}) {{- else }} @@ -232,7 +231,7 @@ func (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(ctx context.Context } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}. -func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { {{- if .IsLegacyPath -}} {{ lower .Resource.Kind }}, ok := newObj.(*{{ .Resource.Kind }}) {{- else }} diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go index 5e7afdace3a..c2b6814e9f2 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go @@ -31,7 +31,7 @@ var ( ) // WebhookSuite scaffolds the file that sets up the webhook tests -type WebhookSuite struct { //nolint:maligned +type WebhookSuite struct { machinery.TemplateMixin machinery.MultiGroupMixin machinery.BoilerplateMixin diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go index 4439a810ce0..55318274c5e 100644 --- a/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go @@ -29,7 +29,7 @@ import ( var _ machinery.Template = &WebhookTest{} // WebhookTest scaffolds the file that sets up the webhook unit tests -type WebhookTest struct { //nolint:maligned +type WebhookTest struct { machinery.TemplateMixin machinery.MultiGroupMixin machinery.BoilerplateMixin @@ -48,15 +48,16 @@ type WebhookTest struct { //nolint:maligned func (f *WebhookTest) SetTemplateDefaults() error { if f.Path == "" { // Deprecated: Remove me when remove go/v4 - baseDir := "api" + const baseDir = "api" + pathAPI := baseDir if !f.IsLegacyPath { - baseDir = filepath.Join("internal", "webhook") + pathAPI = filepath.Join("internal", "webhook") } if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join(baseDir, "%[group]", "%[version]", "%[kind]_webhook_test.go") + f.Path = filepath.Join(pathAPI, "%[group]", "%[version]", "%[kind]_webhook_test.go") } else { - f.Path = filepath.Join(baseDir, "%[version]", "%[kind]_webhook_test.go") + f.Path = filepath.Join(pathAPI, "%[version]", "%[kind]_webhook_test.go") } } f.Path = f.Resource.Replacer().Replace(f.Path) diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go index 446c4596bdc..75dbcb40118 100644 --- a/pkg/plugins/golang/v4/scaffolds/webhook.go +++ b/pkg/plugins/golang/v4/scaffolds/webhook.go @@ -55,12 +55,10 @@ type webhookScaffolder struct { } // NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations -func NewWebhookScaffolder(config config.Config, resource resource.Resource, - force bool, isLegacy bool, -) plugins.Scaffolder { +func NewWebhookScaffolder(cfg config.Config, res resource.Resource, force bool, isLegacy bool) plugins.Scaffolder { return &webhookScaffolder{ - config: config, - resource: resource, + config: cfg, + resource: res, force: force, isLegacy: isLegacy, } @@ -102,17 +100,17 @@ func (s *webhookScaffolder) Scaffold() error { doValidation := s.resource.HasValidationWebhook() doConversion := s.resource.HasConversionWebhook() - if err := s.config.UpdateResource(s.resource); err != nil { + if err = s.config.UpdateResource(s.resource); err != nil { return fmt.Errorf("error updating resource: %w", err) } - if err := scaffold.Execute( + if err = scaffold.Execute( &webhooks.Webhook{Force: s.force, IsLegacyPath: s.isLegacy}, &e2e.WebhookTestUpdater{WireWebhook: true}, &cmd.MainUpdater{WireWebhook: true, IsLegacyPath: s.isLegacy}, &webhooks.WebhookTest{Force: s.force, IsLegacyPath: s.isLegacy}, ); err != nil { - return err + return fmt.Errorf("error updating webhook: %w", err) } if doConversion { @@ -126,24 +124,20 @@ func (s *webhookScaffolder) Scaffold() error { err = pluginutil.InsertCodeIfNotExist(resourceFilePath, "// +kubebuilder:object:root=true", - "\n// +kubebuilder:storageversion\n// +kubebuilder:conversion:hub") + "\n// +kubebuilder:storageversion") if err != nil { log.Errorf("Unable to insert storage version marker "+ - "(// +kubebuilder:storageversion) and the hub conversion (// +kubebuilder:conversion:hub) "+ + "(// +kubebuilder:storageversion)"+ "in file %s: %v", resourceFilePath, err) } - if err := scaffold.Execute( - &api.Hub{Force: s.force}, - ); err != nil { - return err + if err = scaffold.Execute(&api.Hub{Force: s.force}); err != nil { + return fmt.Errorf("error scaffold resource with hub: %w", err) } for _, spoke := range s.resource.Webhooks.Spoke { log.Printf("Scaffolding for spoke version: %s\n", spoke) - if err := scaffold.Execute( - &api.Spoke{Force: s.force, SpokeVersion: spoke}, - ); err != nil { + if err = scaffold.Execute(&api.Spoke{Force: s.force, SpokeVersion: spoke}); err != nil { return fmt.Errorf("failed to scaffold spoke %s: %w", spoke, err) } } @@ -154,10 +148,8 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. if doDefaulting || doValidation { - if err := scaffold.Execute( - &webhooks.WebhookSuite{IsLegacyPath: s.isLegacy}, - ); err != nil { - return err + if err = scaffold.Execute(&webhooks.WebhookSuite{IsLegacyPath: s.isLegacy}); err != nil { + return fmt.Errorf("error scaffold webhook suite: %w", err) } } @@ -169,7 +161,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f log.Warning("Dockerfile is copying internal/controller. To allow copying webhooks, " + "it will be edited, and `internal/controller` will be replaced by `internal/`.") - if err := pluginutil.ReplaceInFile("Dockerfile", "internal/controller", "internal/"); err != nil { + if err = pluginutil.ReplaceInFile("Dockerfile", "internal/controller", "internal/"); err != nil { log.Error("Unable to replace \"internal/controller\" with \"internal/\" in the Dockerfile: ", err) } } diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go index 13c9c9b7c52..8e70df13242 100644 --- a/pkg/plugins/golang/v4/webhook.go +++ b/pkg/plugins/golang/v4/webhook.go @@ -114,14 +114,13 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { p.resource = res if len(p.options.ExternalAPIPath) != 0 && len(p.options.ExternalAPIDomain) != 0 && p.isLegacyPath { - return errors.New("You cannot scaffold webhooks for external types " + - "using the legacy path") + return errors.New("you cannot scaffold webhooks for external types using the legacy path") } for _, spoke := range p.options.Spoke { spoke = strings.TrimSpace(spoke) if !isValidVersion(spoke, res, p.config) { - return fmt.Errorf("invalid spoke version: %s", spoke) + return fmt.Errorf("invalid spoke version %q", spoke) } res.Webhooks.Spoke = append(res.Webhooks.Spoke, spoke) } @@ -129,7 +128,7 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { p.options.UpdateResource(p.resource, p.config) if err := p.resource.Validate(); err != nil { - return err + return fmt.Errorf("error validating resource: %w", err) } if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() { @@ -157,19 +156,23 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force, p.isLegacyPath) scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("failed to scaffold webhook: %w", err) + } + + return nil } func (p *createWebhookSubcommand) PostScaffold() error { err := pluginutil.RunCmd("Update dependencies", "go", "mod", "tidy") if err != nil { - return err + return fmt.Errorf("error updating go dependencies: %w", err) } if p.runMake { err = pluginutil.RunCmd("Running make", "make", "generate") if err != nil { - return err + return fmt.Errorf("error running make generate: %w", err) } } @@ -179,9 +182,9 @@ func (p *createWebhookSubcommand) PostScaffold() error { } // Helper function to validate spoke versions -func isValidVersion(version string, res *resource.Resource, config config.Config) bool { +func isValidVersion(version string, res *resource.Resource, cfg config.Config) bool { // Fetch all resources in the config - resources, err := config.GetResources() + resources, err := cfg.GetResources() if err != nil { return false } diff --git a/pkg/plugins/optional/grafana/v1alpha/commons.go b/pkg/plugins/optional/grafana/v1alpha/commons.go index c1dddcf01f7..3cd97b507cd 100644 --- a/pkg/plugins/optional/grafana/v1alpha/commons.go +++ b/pkg/plugins/optional/grafana/v1alpha/commons.go @@ -18,6 +18,7 @@ package v1alpha import ( "errors" + "fmt" "sigs.k8s.io/kubebuilder/v4/pkg/config" ) @@ -26,15 +27,13 @@ import ( func InsertPluginMetaToConfig(target config.Config, cfg pluginConfig) error { err := target.DecodePluginConfig(pluginKey, cfg) if !errors.As(err, &config.UnsupportedFieldError{}) { - if err != nil && !errors.As(err, &config.PluginKeyNotFoundError{}) { - return err + return fmt.Errorf("error decoding plugin configuration: %w", err) } if err = target.EncodePluginConfig(pluginKey, cfg); err != nil { - return err + return fmt.Errorf("error encoding plugin configuration: %w", err) } - } return nil diff --git a/pkg/plugins/optional/grafana/v1alpha/edit.go b/pkg/plugins/optional/grafana/v1alpha/edit.go index 1993fe138ea..c688c188b83 100644 --- a/pkg/plugins/optional/grafana/v1alpha/edit.go +++ b/pkg/plugins/optional/grafana/v1alpha/edit.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +//nolint:dupl package v1alpha import ( @@ -46,10 +47,14 @@ func (p *editSubcommand) InjectConfig(c config.Config) error { func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { if err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil { - return err + return fmt.Errorf("error inserting project plugin meta to configuration: %w", err) } scaffolder := scaffolds.NewEditScaffolder() scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding edit subcommand: %w", err) + } + + return nil } diff --git a/pkg/plugins/optional/grafana/v1alpha/init.go b/pkg/plugins/optional/grafana/v1alpha/init.go index 1ac9abb0d39..a5abdc0197d 100644 --- a/pkg/plugins/optional/grafana/v1alpha/init.go +++ b/pkg/plugins/optional/grafana/v1alpha/init.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +//nolint:dupl package v1alpha import ( @@ -46,10 +47,14 @@ func (p *initSubcommand) InjectConfig(c config.Config) error { func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { if err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil { - return err + return fmt.Errorf("error inserting project plugin meta to configuration: %w", err) } scaffolder := scaffolds.NewInitScaffolder() scaffolder.InjectFS(fs) - return scaffolder.Scaffold() + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding init subcommand: %w", err) + } + + return nil } diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go index f2a409de092..2b2ef84df20 100644 --- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go +++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go @@ -77,20 +77,20 @@ func loadConfig(configPath string) ([]templates.CustomMetricItem, error) { return nil, fmt.Errorf("could not close config.yaml: %w", err) } - return items, err + return items, nil } func configReader(reader io.Reader) ([]templates.CustomMetricItem, error) { yamlFile, err := io.ReadAll(reader) if err != nil { - return nil, err + return nil, fmt.Errorf("error reading config.yaml: %w", err) } config := templates.CustomMetricsConfig{} err = yaml.Unmarshal(yamlFile, &config) if err != nil { - return nil, err + return nil, fmt.Errorf("error parsing config.yaml: %w", err) } validatedMetricItems := validateCustomMetricItems(config.CustomMetrics) @@ -100,7 +100,7 @@ func configReader(reader io.Reader) ([]templates.CustomMetricItem, error) { func validateCustomMetricItems(rawItems []templates.CustomMetricItem) []templates.CustomMetricItem { // 1. Filter items of missing `Metric` or `Type` - filterResult := []templates.CustomMetricItem{} + var filterResult []templates.CustomMetricItem for _, item := range rawItems { if hasFields(item) { filterResult = append(filterResult, item) @@ -170,7 +170,7 @@ func (s *editScaffolder) Scaffold() error { // Initialize the machinery.Scaffold that will write the files to disk scaffold := machinery.NewScaffold(s.fs) - configPath := string(configFilePath) + configPath := configFilePath templatesBuilder := []machinery.Builder{ &templates.RuntimeManifest{}, @@ -185,5 +185,9 @@ func (s *editScaffolder) Scaffold() error { _, _ = fmt.Fprintf(os.Stderr, "Error on scaffolding manifest for custom metris:\n%v", err) } - return scaffold.Execute(templatesBuilder...) + if err = scaffold.Execute(templatesBuilder...); err != nil { + return fmt.Errorf("error scaffolding Grafana manifests: %w", err) + } + + return nil } diff --git a/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go b/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go index 7c220a578b4..32c0439df82 100644 --- a/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go +++ b/pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go @@ -17,6 +17,8 @@ limitations under the License. package scaffolds import ( + "fmt" + log "github.com/sirupsen/logrus" "sigs.k8s.io/kubebuilder/v4/pkg/machinery" @@ -48,9 +50,14 @@ func (s *initScaffolder) Scaffold() error { // Initialize the machinery.Scaffold that will write the files to disk scaffold := machinery.NewScaffold(s.fs) - return scaffold.Execute( + err := scaffold.Execute( &templates.RuntimeManifest{}, &templates.ResourcesManifest{}, - &templates.CustomMetricsConfigManifest{ConfigPath: string(configFilePath)}, + &templates.CustomMetricsConfigManifest{ConfigPath: configFilePath}, ) + if err != nil { + return fmt.Errorf("error scaffolding Grafana memanifests: %w", err) + } + + return nil } diff --git a/pkg/plugins/optional/helm/v1alpha/commons.go b/pkg/plugins/optional/helm/v1alpha/commons.go index c6942322ec7..02370c48dae 100644 --- a/pkg/plugins/optional/helm/v1alpha/commons.go +++ b/pkg/plugins/optional/helm/v1alpha/commons.go @@ -18,6 +18,7 @@ package v1alpha import ( "errors" + "fmt" "sigs.k8s.io/kubebuilder/v4/pkg/config" ) @@ -26,10 +27,10 @@ func insertPluginMetaToConfig(target config.Config, cfg pluginConfig) error { err := target.DecodePluginConfig(pluginKey, cfg) if !errors.As(err, &config.UnsupportedFieldError{}) { if err != nil && !errors.As(err, &config.PluginKeyNotFoundError{}) { - return err + return fmt.Errorf("error decoding plugin configuration: %w", err) } if err = target.EncodePluginConfig(pluginKey, cfg); err != nil { - return err + return fmt.Errorf("error encoding plugin config: %w", err) } } diff --git a/pkg/plugins/optional/helm/v1alpha/edit.go b/pkg/plugins/optional/helm/v1alpha/edit.go index 1a497eb0c13..0ac7c57b01a 100644 --- a/pkg/plugins/optional/helm/v1alpha/edit.go +++ b/pkg/plugins/optional/helm/v1alpha/edit.go @@ -74,11 +74,11 @@ func (p *editSubcommand) InjectConfig(c config.Config) error { } func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { - scaffolder := scaffolds.NewInitHelmScaffolder(p.config, p.force) + scaffolder := scaffolds.NewHelmScaffolder(p.config, p.force) scaffolder.InjectFS(fs) err := scaffolder.Scaffold() if err != nil { - return err + return fmt.Errorf("error scaffolding Helm chart: %w", err) } // Track the resources following a declarative approach diff --git a/pkg/plugins/optional/helm/v1alpha/init.go b/pkg/plugins/optional/helm/v1alpha/init.go deleted file mode 100644 index cfeba16a856..00000000000 --- a/pkg/plugins/optional/helm/v1alpha/init.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 v1alpha - -import ( - "fmt" - - "sigs.k8s.io/kubebuilder/v4/pkg/config" - "sigs.k8s.io/kubebuilder/v4/pkg/machinery" - "sigs.k8s.io/kubebuilder/v4/pkg/plugin" - "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds" -) - -var _ plugin.InitSubcommand = &initSubcommand{} - -type initSubcommand struct { - config config.Config -} - -func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { - subcmdMeta.Description = `Initialize a helm chart to distribute the project under dist/ -` - subcmdMeta.Examples = fmt.Sprintf(`# Initialize a helm chart to distribute the project under dist/ - %[1]s init --plugins=%[2]s - -**IMPORTANT** You must use %[1]s edit --plugins=%[2]s to update the chart when changes are made. -`, cliMeta.CommandName, plugin.KeyFor(Plugin{})) -} - -func (p *initSubcommand) InjectConfig(c config.Config) error { - p.config = c - return nil -} - -func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { - scaffolder := scaffolds.NewInitHelmScaffolder(p.config, false) - scaffolder.InjectFS(fs) - err := scaffolder.Scaffold() - if err != nil { - return err - } - - // Track the resources following a declarative approach - return insertPluginMetaToConfig(p.config, pluginConfig{}) -} diff --git a/pkg/plugins/optional/helm/v1alpha/plugin.go b/pkg/plugins/optional/helm/v1alpha/plugin.go index 41d97de288d..c56b7c559b8 100644 --- a/pkg/plugins/optional/helm/v1alpha/plugin.go +++ b/pkg/plugins/optional/helm/v1alpha/plugin.go @@ -34,14 +34,10 @@ var ( // Plugin implements the plugin.Full interface type Plugin struct { - initSubcommand editSubcommand } -var ( - _ plugin.Init = Plugin{} - _ plugin.Edit = Plugin{} -) +var _ plugin.Edit = Plugin{} type pluginConfig struct{} @@ -54,9 +50,6 @@ func (Plugin) Version() plugin.Version { return pluginVersion } // SupportedProjectVersions returns an array with all project versions supported by the plugin func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } -// GetInitSubcommand will return the subcommand which is responsible for initializing and scaffolding helm manifests -func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } - // GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a helm chart func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go similarity index 89% rename from pkg/plugins/optional/helm/v1alpha/scaffolds/init.go rename to pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go index e92713998ef..8ac971e380d 100644 --- a/pkg/plugins/optional/helm/v1alpha/scaffolds/init.go +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go @@ -42,9 +42,9 @@ import ( "sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github" ) -var _ plugins.Scaffolder = &initScaffolder{} +var _ plugins.Scaffolder = &editScaffolder{} -type initScaffolder struct { +type editScaffolder struct { config config.Config fs machinery.Filesystem @@ -52,21 +52,21 @@ type initScaffolder struct { force bool } -// NewInitHelmScaffolder returns a new Scaffolder for HelmPlugin -func NewInitHelmScaffolder(cfg config.Config, force bool) plugins.Scaffolder { - return &initScaffolder{ +// NewHelmScaffolder returns a new Scaffolder for HelmPlugin +func NewHelmScaffolder(cfg config.Config, force bool) plugins.Scaffolder { + return &editScaffolder{ config: cfg, force: force, } } // InjectFS implements cmdutil.Scaffolder -func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { +func (s *editScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs } // Scaffold scaffolds the Helm chart with the necessary files. -func (s *initScaffolder) Scaffold() error { +func (s *editScaffolder) Scaffold() error { log.Println("Generating Helm Chart to distribute project") imagesEnvVars := s.getDeployImagesEnvVars() @@ -118,13 +118,13 @@ func (s *initScaffolder) Scaffold() error { } if err = scaffold.Execute(buildScaffold...); err != nil { - return fmt.Errorf("error scaffolding helm-chart manifests: %v", err) + return fmt.Errorf("error scaffolding helm-chart manifests: %w", err) } // Copy relevant files from config/ to dist/chart/templates/ err = s.copyConfigFiles() if err != nil { - return fmt.Errorf("failed to copy manifests from config to dist/chart/templates/: %v", err) + return fmt.Errorf("failed to copy manifests from config to dist/chart/templates/: %w", err) } return nil @@ -132,7 +132,7 @@ func (s *initScaffolder) Scaffold() error { // getDeployImagesEnvVars will return the values to append the envvars for projects // which has the APIs scaffolded with DeployImage plugin -func (s *initScaffolder) getDeployImagesEnvVars() map[string]string { +func (s *editScaffolder) getDeployImagesEnvVars() map[string]string { deployImages := make(map[string]string) pluginConfig := struct { @@ -157,7 +157,7 @@ func (s *initScaffolder) getDeployImagesEnvVars() map[string]string { // extractWebhooksFromGeneratedFiles parses the files generated by controller-gen under // config/webhooks and created Mutating and Validating helper structures to // generate the webhook manifest for the helm-chart -func (s *initScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks []templateswebhooks.DataWebhook, +func (s *editScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks []templateswebhooks.DataWebhook, validatingWebhooks []templateswebhooks.DataWebhook, err error, ) { manifestFile := "config/webhook/manifests.yaml" @@ -170,7 +170,7 @@ func (s *initScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks [ content, err := os.ReadFile(manifestFile) if err != nil { return nil, nil, - fmt.Errorf("failed to read %s: %w", manifestFile, err) + fmt.Errorf("failed to read %q: %w", manifestFile, err) } docs := strings.Split(string(content), "---") @@ -214,9 +214,10 @@ func (s *initScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks [ Rules: w.Rules, } - if webhookConfig.Kind == "MutatingWebhookConfiguration" { + switch webhookConfig.Kind { + case "MutatingWebhookConfiguration": mutatingWebhooks = append(mutatingWebhooks, webhook) - } else if webhookConfig.Kind == "ValidatingWebhookConfiguration" { + case "ValidatingWebhookConfiguration": validatingWebhooks = append(validatingWebhooks, webhook) } } @@ -226,7 +227,7 @@ func (s *initScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks [ } // Helper function to copy files from config/ to dist/chart/templates/ -func (s *initScaffolder) copyConfigFiles() error { +func (s *editScaffolder) copyConfigFiles() error { configDirs := []struct { SrcDir string DestDir string @@ -246,7 +247,7 @@ func (s *initScaffolder) copyConfigFiles() error { files, err := filepath.Glob(filepath.Join(dir.SrcDir, "*.yaml")) if err != nil { - return err + return fmt.Errorf("failed finding files in %q: %w", dir.SrcDir, err) } // Skip processing if the directory is empty (no matching files) @@ -255,8 +256,8 @@ func (s *initScaffolder) copyConfigFiles() error { } // Ensure destination directory exists - if err := os.MkdirAll(dir.DestDir, os.ModePerm); err != nil { - return fmt.Errorf("failed to create directory %s: %v", dir.DestDir, err) + if err := os.MkdirAll(dir.DestDir, 0o755); err != nil { + return fmt.Errorf("failed to create directory %q: %w", dir.DestDir, err) } for _, srcFile := range files { @@ -291,13 +292,13 @@ func (s *initScaffolder) copyConfigFiles() error { func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasConvertionalWebhook bool) error { if _, err := os.Stat(srcFile); os.IsNotExist(err) { log.Printf("Source file does not exist: %s", srcFile) - return err + return fmt.Errorf("source file does not exist %q: %w", srcFile, err) } content, err := os.ReadFile(srcFile) if err != nil { log.Printf("Error reading source file: %s", srcFile) - return err + return fmt.Errorf("failed to read file %q: %w", srcFile, err) } contentStr := string(content) @@ -310,16 +311,16 @@ func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasCon // Apply RBAC-specific replacements if subDir == "rbac" { - contentStr = strings.Replace(contentStr, + contentStr = strings.ReplaceAll(contentStr, "name: controller-manager", - "name: {{ .Values.controllerManager.serviceAccountName }}", -1) + "name: {{ .Values.controllerManager.serviceAccountName }}") contentStr = strings.Replace(contentStr, "name: metrics-reader", fmt.Sprintf("name: %s-metrics-reader", projectName), 1) - contentStr = strings.Replace(contentStr, + contentStr = strings.ReplaceAll(contentStr, "name: metrics-auth-role", - fmt.Sprintf("name: %s-metrics-auth-role", projectName), -1) + fmt.Sprintf("name: %s-metrics-auth-role", projectName)) contentStr = strings.Replace(contentStr, "name: metrics-auth-rolebinding", fmt.Sprintf("name: %s-metrics-auth-rolebinding", projectName), 1) @@ -337,15 +338,15 @@ func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasCon {{- end }} {{- end }}`, 1) } - contentStr = strings.Replace(contentStr, + contentStr = strings.ReplaceAll(contentStr, "name: leader-election-role", - fmt.Sprintf("name: %s-leader-election-role", projectName), -1) + fmt.Sprintf("name: %s-leader-election-role", projectName)) contentStr = strings.Replace(contentStr, "name: leader-election-rolebinding", fmt.Sprintf("name: %s-leader-election-rolebinding", projectName), 1) - contentStr = strings.Replace(contentStr, + contentStr = strings.ReplaceAll(contentStr, "name: manager-role", - fmt.Sprintf("name: %s-manager-role", projectName), -1) + fmt.Sprintf("name: %s-manager-role", projectName)) contentStr = strings.Replace(contentStr, "name: manager-rolebinding", fmt.Sprintf("name: %s-manager-rolebinding", projectName), 1) @@ -423,6 +424,9 @@ func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasCon labels: {{- include "chart.labels" . | nindent 4 }}`, 1) + // Append project name to webhook service name + contentStr = strings.ReplaceAll(contentStr, "name: webhook-service", "name: "+projectName+"-webhook-service") + var wrappedContent string if isMetricRBACFile(subDir, srcFile) { wrappedContent = fmt.Sprintf( @@ -432,14 +436,14 @@ func copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasCon "{{- if .Values.%s.enable }}\n%s{{- end -}}\n", subDir, contentStr) } - if err = os.MkdirAll(filepath.Dir(destFile), os.ModePerm); err != nil { - return err + if err = os.MkdirAll(filepath.Dir(destFile), 0o755); err != nil { + return fmt.Errorf("error creating directory %q: %w", filepath.Dir(destFile), err) } - err = os.WriteFile(destFile, []byte(wrappedContent), os.ModePerm) + err = os.WriteFile(destFile, []byte(wrappedContent), 0o644) if err != nil { log.Printf("Error writing destination file: %s", destFile) - return err + return fmt.Errorf("error writing destination file %q: %w", destFile, err) } log.Printf("Successfully copied %s to %s", srcFile, destFile) @@ -462,7 +466,7 @@ func getCRDPatchContent(kind, group string) (string, bool, error) { groupKindPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*%s*.yaml", group, kind) patchFiles, err := filepath.Glob(groupKindPattern) if err != nil { - return "", false, fmt.Errorf("failed to list patches: %v", err) + return "", false, fmt.Errorf("failed to list patches: %w", err) } // If no group-specific patch found, search for patches that contain only "webhook" and the kind @@ -470,7 +474,7 @@ func getCRDPatchContent(kind, group string) (string, bool, error) { kindOnlyPattern := fmt.Sprintf("config/crd/patches/webhook_*%s*.yaml", kind) patchFiles, err = filepath.Glob(kindOnlyPattern) if err != nil { - return "", false, fmt.Errorf("failed to list patches: %v", err) + return "", false, fmt.Errorf("failed to list patches: %w", err) } } @@ -478,7 +482,7 @@ func getCRDPatchContent(kind, group string) (string, bool, error) { if len(patchFiles) > 0 { patchContent, err := os.ReadFile(patchFiles[0]) if err != nil { - return "", false, fmt.Errorf("failed to read patch file %s: %v", patchFiles[0], err) + return "", false, fmt.Errorf("failed to read patch file %q: %w", patchFiles[0], err) } return string(patchContent), true, nil } diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go index fe90d20261c..06edd0d0757 100644 --- a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go @@ -50,6 +50,7 @@ metadata: namespace: {{ "{{ .Release.Namespace }}" }} labels: {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + control-plane: controller-manager spec: ports: - port: 8443 diff --git a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go index a4e90fb66c9..eb157cad611 100644 --- a/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go +++ b/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go @@ -49,6 +49,7 @@ kind: ServiceMonitor metadata: labels: {{ "{{- include \"chart.labels\" . | nindent 4 }}" }} + control-plane: controller-manager name: {{ .ProjectName }}-controller-manager-metrics-monitor namespace: {{ "{{ .Release.Namespace }}" }} spec: diff --git a/test/common.sh b/test/common.sh index e85189ddd6e..7c54cdb3a61 100644 --- a/test/common.sh +++ b/test/common.sh @@ -35,6 +35,7 @@ function convert_to_tools_ver { "1.30") echo "1.30.0";; "1.31") echo "1.31.0";; "1.32") echo "1.32.0";; + "1.33") echo "1.33.0";; *) echo "k8s version $k8s_ver not supported" exit 1 @@ -54,9 +55,9 @@ if [ -n "$TRACE" ]; then set -x fi -export KIND_K8S_VERSION="${KIND_K8S_VERSION:-"v1.31.0"}" +export KIND_K8S_VERSION="${KIND_K8S_VERSION:-"v1.33.0"}" tools_k8s_version=$(convert_to_tools_ver "${KIND_K8S_VERSION#v*}") -kind_version=0.22.0 +kind_version=0.29.0 goarch=amd64 if [[ "$OSTYPE" == "linux-gnu" ]]; then diff --git a/test/e2e/alphagenerate/generate_test.go b/test/e2e/alphagenerate/generate_test.go index 67e209fdd37..bee565f1637 100644 --- a/test/e2e/alphagenerate/generate_test.go +++ b/test/e2e/alphagenerate/generate_test.go @@ -74,21 +74,24 @@ var _ = Describe("kubebuilder", func() { validateProjectFile(kbc, filepath.Join(kbc.Dir, "PROJECT")) }) - It("should regenerate project with grafana plugin with success", func() { - generateProjectWithGrafanaPlugin(kbc) - regenerateProjectWith(kbc, projectOutputDir) - validateGrafanaPlugin(projectFilePath) - }) + It("should regenerate project with plugins with success", func() { + By("Enabling the Grafana plugin") + err := kbc.Edit("--plugins", "grafana.kubebuilder.io/v1-alpha") + Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Grafana Plugin") - It("should regenerate project with DeployImage plugin with success", func() { - generateProjectWithDeployImagePlugin(kbc) - regenerateProjectWith(kbc, projectOutputDir) - validateDeployImagePlugin(projectFilePath) - }) + By("Generate API with Deploy Image plugin") + generateAPIWithDeployImage(kbc) + + By("Enabling Helm plugin") + err = kbc.Edit("--plugins", "helm.kubebuilder.io/v1-alpha") + Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Helm Plugin") - It("should regenerate project with helm plugin with success", func() { - generateProjectWithHelmPlugin(kbc) + By("Re-generating the project with plugins") regenerateProjectWith(kbc, projectOutputDir) + + By("By validating the expected scaffolded files") + validateGrafanaPlugin(projectFilePath) + validateDeployImagePlugin(projectFilePath) validateHelmPlugin(projectFilePath) }) }) @@ -200,19 +203,7 @@ func regenerateProjectWith(kbc *utils.TestContext, projectOutputDir string) { Expect(err).NotTo(HaveOccurred(), "Failed to regenerate project") } -func generateProjectWithGrafanaPlugin(kbc *utils.TestContext) { - By("editing project to enable Grafana plugin") - err := kbc.Edit("--plugins", "grafana.kubebuilder.io/v1-alpha") - Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Grafana Plugin") -} - -func generateProjectWithHelmPlugin(kbc *utils.TestContext) { - By("editing project to enable Helm plugin") - err := kbc.Edit("--plugins", "helm.kubebuilder.io/v1-alpha") - Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Helm Plugin") -} - -func generateProjectWithDeployImagePlugin(kbc *utils.TestContext) { +func generateAPIWithDeployImage(kbc *utils.TestContext) { By("creating an API with DeployImage plugin") err := kbc.CreateAPI( "--group", "crew", diff --git a/test/e2e/alphaupdate/e2e_suite_test.go b/test/e2e/alphaupdate/e2e_suite_test.go new file mode 100644 index 00000000000..e2918732619 --- /dev/null +++ b/test/e2e/alphaupdate/e2e_suite_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2025 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 alphaupdate + +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 kubebuilder suite test for the alpha update command\n") + RunSpecs(t, "Kubebuilder alpha update suite") +} diff --git a/test/e2e/alphaupdate/update_test.go b/test/e2e/alphaupdate/update_test.go new file mode 100644 index 00000000000..537c361a592 --- /dev/null +++ b/test/e2e/alphaupdate/update_test.go @@ -0,0 +1,285 @@ +/* +Copyright 2025 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 alphaupdate + +import ( + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" + "sigs.k8s.io/kubebuilder/v4/test/e2e/utils" +) + +const ( + fromVersion = "v4.5.2" + toVersion = "v4.6.0" + + // Binary patterns for cleanup + binFromVersionPattern = "/tmp/kubebuilder" + fromVersion + "-*" + binToVersionPattern = "/tmp/kubebuilder" + toVersion + "-*" +) + +var _ = Describe("kubebuilder", func() { + Context("alpha update", func() { + var ( + mockProjectDir string + binFromVersionPath string + kbc *utils.TestContext + ) + + BeforeEach(func() { + var err error + By("setting up test context with current kubebuilder binary") + kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on") + Expect(err).NotTo(HaveOccurred()) + Expect(kbc.Prepare()).To(Succeed()) + + By("creating isolated mock project directory in /tmp to avoid git conflicts") + mockProjectDir, err = os.MkdirTemp("/tmp", "kubebuilder-mock-project-") + Expect(err).NotTo(HaveOccurred()) + + By("downloading kubebuilder v4.5.2 binary to isolated /tmp directory") + binFromVersionPath, err = downloadKubebuilder() + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + By("cleaning up test artifacts") + + _ = os.RemoveAll(mockProjectDir) + _ = os.RemoveAll(filepath.Dir(binFromVersionPath)) + + // Clean up kubebuilder alpha update downloaded binaries + binaryPatterns := []string{ + binFromVersionPattern, + binToVersionPattern, + } + + for _, pattern := range binaryPatterns { + matches, _ := filepath.Glob(pattern) + for _, path := range matches { + _ = os.RemoveAll(path) + } + } + + // Clean up TestContext + if kbc != nil { + kbc.Destroy() + } + }) + + It("should update project from v4.5.2 to v4.6.0 preserving custom code", func() { + By("creating mock project with kubebuilder v4.5.2") + createMockProject(mockProjectDir, binFromVersionPath) + + By("injecting custom code in API and controller") + injectCustomCode(mockProjectDir) + + By("initializing git repository and committing mock project") + initializeGitRepo(mockProjectDir) + + By("running alpha update from v4.5.2 to v4.6.0") + runAlphaUpdate(mockProjectDir, kbc) + + By("validating custom code preservation") + validateCustomCodePreservation(mockProjectDir) + }) + }) +}) + +// downloadKubebuilder downloads the --from-version kubebuilder binary to a temporary directory +func downloadKubebuilder() (string, error) { + binaryDir, err := os.MkdirTemp("", "kubebuilder-v4.5.2-") + if err != nil { + return "", fmt.Errorf("failed to create binary directory: %w", err) + } + + url := fmt.Sprintf( + "https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s", + fromVersion, + runtime.GOOS, + runtime.GOARCH, + ) + binaryPath := filepath.Join(binaryDir, "kubebuilder") + + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to download kubebuilder %s: %w", fromVersion, err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to download kubebuilder %s: HTTP %d", fromVersion, resp.StatusCode) + } + + file, err := os.Create(binaryPath) + if err != nil { + return "", fmt.Errorf("failed to create binary file: %w", err) + } + defer func() { _ = file.Close() }() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", fmt.Errorf("failed to write binary: %w", err) + } + + err = os.Chmod(binaryPath, 0o755) + if err != nil { + return "", fmt.Errorf("failed to make binary executable: %w", err) + } + + return binaryPath, nil +} + +func createMockProject(projectDir, binaryPath string) { + err := os.Chdir(projectDir) + Expect(err).NotTo(HaveOccurred()) + + By("running kubebuilder init") + cmd := exec.Command(binaryPath, "init", "--domain", "example.com", "--repo", "github.com/example/test-operator") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + By("running kubebuilder create api") + cmd = exec.Command( + binaryPath, "create", "api", + "--group", "webapp", + "--version", "v1", + "--kind", "TestOperator", + "--resource", "--controller", + ) + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + By("running make generate manifests") + cmd = exec.Command("make", "generate", "manifests") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) +} + +func injectCustomCode(projectDir string) { + typesFile := filepath.Join(projectDir, "api", "v1", "testoperator_types.go") + err := pluginutil.InsertCode( + typesFile, + "Foo string `json:\"foo,omitempty\"`", + ` + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=3 + // +kubebuilder:default=1 + // Size is the size of the memcached deployment + Size int32 `+"`json:\"size,omitempty\"`", + ) + Expect(err).NotTo(HaveOccurred()) + controllerFile := filepath.Join(projectDir, "internal", "controller", "testoperator_controller.go") + err = pluginutil.InsertCode( + controllerFile, + "// TODO(user): your logic here", + `// Custom reconciliation logic + log := ctrl.LoggerFrom(ctx) + log.Info("Reconciling TestOperator") + + // Fetch the TestOperator instance + testOperator := &webappv1.TestOperator{} + err := r.Get(ctx, req.NamespacedName, testOperator) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + // Custom logic: log the size field + log.Info("TestOperator size", "size", testOperator.Spec.Size)`, + ) + Expect(err).NotTo(HaveOccurred()) +} + +func initializeGitRepo(projectDir string) { + By("initializing git repository") + cmd := exec.Command("git", "init") + cmd.Dir = projectDir + _, err := cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + cmd = exec.Command("git", "config", "user.email", "test@example.com") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + cmd = exec.Command("git", "config", "user.name", "Test User") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + By("adding all files to git") + cmd = exec.Command("git", "add", "-A") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + By("committing initial project state") + cmd = exec.Command("git", "commit", "-m", "Initial project with custom code") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + + By("ensuring main branch exists and is current") + cmd = exec.Command("git", "checkout", "-b", "main") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + if err != nil { + // If main branch already exists, just switch to it + cmd = exec.Command("git", "checkout", "main") + cmd.Dir = projectDir + _, err = cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred()) + } +} + +func runAlphaUpdate(projectDir string, kbc *utils.TestContext) { + err := os.Chdir(projectDir) + Expect(err).NotTo(HaveOccurred()) + + // Use TestContext to run alpha update command + cmd := exec.Command(kbc.BinaryName, "alpha", "update", + "--from-version", fromVersion, "--to-version", toVersion, "--from-branch", "main") + cmd.Dir = projectDir + output, err := cmd.CombinedOutput() + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Alpha update failed: %s", string(output))) +} + +func validateCustomCodePreservation(projectDir string) { + typesFile := filepath.Join(projectDir, "api", "v1", "testoperator_types.go") + content, err := os.ReadFile(typesFile) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Size int32 `json:\"size,omitempty\"`")) + Expect(string(content)).To(ContainSubstring("Size is the size of the memcached deployment")) + + controllerFile := filepath.Join(projectDir, "internal", "controller", "testoperator_controller.go") + content, err = os.ReadFile(controllerFile) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Custom reconciliation logic")) + Expect(string(content)).To(ContainSubstring("log.Info(\"Reconciling TestOperator\")")) + Expect(string(content)).To(ContainSubstring("log.Info(\"TestOperator size\", \"size\", testOperator.Spec.Size)")) +} diff --git a/test/e2e/kind-config.yaml b/test/e2e/kind-config.yaml index 3e5262c0e6e..407c521f692 100644 --- a/test/e2e/kind-config.yaml +++ b/test/e2e/kind-config.yaml @@ -15,6 +15,6 @@ kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: - disableDefaultCNI: true # Disable the default CNI so that we can test NetworkPolicies + disableDefaultCNI: false # Let it use default CNI so we can test NetworkPolicies nodes: - role: control-plane diff --git a/test/e2e/setup.sh b/test/e2e/setup.sh index ef8fe2f975a..231c335127d 100755 --- a/test/e2e/setup.sh +++ b/test/e2e/setup.sh @@ -39,8 +39,6 @@ function create_cluster { fi echo "Creating cluster..." kind create cluster -v 4 --name $KIND_CLUSTER --retain --wait=1m --config ${kind_config} --image=kindest/node:$1 - echo "Installing Calico..." - kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml fi } @@ -66,8 +64,6 @@ function test_cluster { docker pull busybox:1.36.1 kind load docker-image --name $KIND_CLUSTER busybox:1.36.1 - go test $(dirname "$0")/grafana $flags -timeout 30m go test $(dirname "$0")/deployimage $flags -timeout 30m go test $(dirname "$0")/v4 $flags -timeout 30m - go test $(dirname "$0")/alphagenerate $flags -timeout 30m } diff --git a/test/e2e/utils/kubectl.go b/test/e2e/utils/kubectl.go index 76ada3053f8..909a833b576 100644 --- a/test/e2e/utils/kubectl.go +++ b/test/e2e/utils/kubectl.go @@ -19,6 +19,7 @@ package utils import ( "encoding/json" "errors" + "fmt" "os/exec" "strconv" "strings" @@ -114,12 +115,12 @@ func (vi VersionInfo) GetMinorInt() uint64 { return vi.minor } func (vi *VersionInfo) parseVersionInts() (err error) { if vi.Major != "" { if vi.major, err = strconv.ParseUint(vi.Major, 10, 64); err != nil { - return err + return fmt.Errorf("error parsing major version %q: %w", vi.Major, err) } } if vi.Minor != "" { if vi.minor, err = strconv.ParseUint(vi.Minor, 10, 64); err != nil { - return err + return fmt.Errorf("error parsing minor version %q: %w", vi.Minor, err) } } return nil @@ -142,18 +143,18 @@ func (v *KubernetesVersion) prepare() error { func (k *Kubectl) Version() (ver KubernetesVersion, err error) { out, err := k.Command("version", "-o", "json") if err != nil { - return KubernetesVersion{}, err + return KubernetesVersion{}, fmt.Errorf("error getting kubernetes version: %w", err) } - if err := ver.decode(out); err != nil { - return KubernetesVersion{}, err + if decodeErr := ver.decode(out); decodeErr != nil { + return KubernetesVersion{}, fmt.Errorf("error parsing kubernetes version: %w", decodeErr) } return ver, nil } -func (v *KubernetesVersion) decode(out string) (err error) { +func (v *KubernetesVersion) decode(out string) error { dec := json.NewDecoder(strings.NewReader(out)) if err := dec.Decode(&v); err != nil { - return err + return fmt.Errorf("error decoding kubernetes version: %w", err) } return v.prepare() } diff --git a/test/e2e/utils/test_context.go b/test/e2e/utils/test_context.go index 36bc186d0cf..0b4f6a8108c 100644 --- a/test/e2e/utils/test_context.go +++ b/test/e2e/utils/test_context.go @@ -27,6 +27,7 @@ import ( log "github.com/sirupsen/logrus" "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" + //nolint:staticcheck . "github.com/onsi/ginkgo/v2" ) @@ -59,7 +60,7 @@ type TestContext struct { func NewTestContext(binaryName string, env ...string) (*TestContext, error) { testSuffix, err := util.RandomSuffix() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to generate random suffix: %w", err) } cc := &CmdContext{ @@ -72,14 +73,28 @@ func NewTestContext(binaryName string, env ...string) (*TestContext, error) { ServiceAccount: fmt.Sprintf("e2e-%s-controller-manager", testSuffix), CmdContext: cc, } - k8sVersion, err := kubectl.Version() + var k8sVersion *KubernetesVersion + v, err := kubectl.Version() if err != nil { - return nil, err + _, _ = fmt.Fprintf(GinkgoWriter, "warning: failed to get kubernetes version: %v\n", err) + k8sVersion = &KubernetesVersion{ + ClientVersion: VersionInfo{ + Major: "1", + Minor: "0", + GitVersion: "v1.0.0-fake", + }, + ServerVersion: VersionInfo{ + Major: "1", + Minor: "0", + GitVersion: "v1.0.0-fake", + }, + } + } else { + k8sVersion = &v } - // Set CmdContext.Dir after running Kubectl.Version() because dir does not exist yet. if cc.Dir, err = filepath.Abs("e2e-" + testSuffix); err != nil { - return nil, err + return nil, fmt.Errorf("failed to determine absolute path to %q: %w", "e2e-"+testSuffix, err) } return &TestContext{ @@ -92,7 +107,7 @@ func NewTestContext(binaryName string, env ...string) (*TestContext, error) { ImageName: "e2e-test/controller-manager:" + testSuffix, CmdContext: cc, Kubectl: kubectl, - K8sVersion: &k8sVersion, + K8sVersion: k8sVersion, BinaryName: binaryName, }, nil } @@ -108,13 +123,17 @@ func (t *TestContext) Prepare() error { for _, toolName := range []string{"controller-gen", "kustomize"} { if toolPath, err := exec.LookPath(toolName); err == nil { if err := os.RemoveAll(toolPath); err != nil { - return err + return fmt.Errorf("failed to remove %q: %w", toolName, err) } } } _, _ = fmt.Fprintf(GinkgoWriter, "preparing testing directory: %s\n", t.Dir) - return os.MkdirAll(t.Dir, 0o755) + if err := os.MkdirAll(t.Dir, 0o755); err != nil { + return fmt.Errorf("error creating test directory %q: %w", t.Dir, err) + } + + return nil } // makeCertManagerURL returns a kubectl-able URL for the cert-manager bundle. @@ -307,7 +326,7 @@ func (cc *CmdContext) Run(cmd *exec.Cmd) ([]byte, error) { _, _ = 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, fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return output, nil @@ -320,13 +339,13 @@ func (t *TestContext) AllowProjectBeMultiGroup() error { ` projectBytes, err := os.ReadFile(filepath.Join(t.Dir, "PROJECT")) if err != nil { - return err + return fmt.Errorf("cannot read project file: %w", err) } projectBytes = append([]byte(multiGroup), projectBytes...) err = os.WriteFile(filepath.Join(t.Dir, "PROJECT"), projectBytes, 0o644) if err != nil { - return err + return fmt.Errorf("could not write to project file: %w", err) } return nil } @@ -360,11 +379,6 @@ func (t *TestContext) UninstallHelmRelease() error { if err != nil { return err } - - if _, err := t.Kubectl.Wait(false, "namespace", ns, "--for=delete", "--timeout=2m"); err != nil { - log.Printf("failed to wait for namespace deletion: %s", err) - } - return nil } diff --git a/test/e2e/utils/webhooks.go b/test/e2e/utils/webhooks.go index 41f944ba882..7262cc76a1f 100644 --- a/test/e2e/utils/webhooks.go +++ b/test/e2e/utils/webhooks.go @@ -28,7 +28,7 @@ func ImplementWebhooks(filename, lowerKind string) error { //nolint:gosec // false positive bs, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("error reading webhooks file %q: %w", filename, err) } str := string(bs) @@ -38,7 +38,7 @@ func ImplementWebhooks(filename, lowerKind string) error { `import ( "errors"`) if err != nil { - return err + return fmt.Errorf("error replacing imports in webhooks file %q: %w", filename, err) } // implement defaulting webhook logic @@ -51,7 +51,7 @@ func ImplementWebhooks(filename, lowerKind string) error { replace, ) if err != nil { - return err + return fmt.Errorf("error replacing default logic in webhooks file %q: %w", filename, err) } // implement validation webhook logic @@ -62,7 +62,7 @@ func ImplementWebhooks(filename, lowerKind string) error { return nil, errors.New(".spec.count must >= 0") }`, lowerKind)) if err != nil { - return err + return fmt.Errorf("error replacing validation logic in webhooks file %q: %w", filename, err) } str, err = util.EnsureExistAndReplace( str, @@ -71,8 +71,12 @@ func ImplementWebhooks(filename, lowerKind string) error { return nil, errors.New(".spec.count must >= 0") }`, lowerKind)) if err != nil { - return err + return fmt.Errorf("error replacing validation logic in webhooks file %q: %w", filename, err) } //nolint:gosec // false positive - return os.WriteFile(filename, []byte(str), 0o644) + if writeFileErr := os.WriteFile(filename, []byte(str), 0o644); writeFileErr != nil { + return fmt.Errorf("error writing webhooks file %q: %w", filename, writeFileErr) + } + + return nil } diff --git a/test/e2e/v4/generate_test.go b/test/e2e/v4/generate_test.go index de7a50d586e..0f29b12c2c5 100644 --- a/test/e2e/v4/generate_test.go +++ b/test/e2e/v4/generate_test.go @@ -53,16 +53,9 @@ func GenerateV4(kbc *utils.TestContext) { scaffoldConversionWebhook(kbc) - ExpectWithOffset(1, pluginutil.UncommentCode( - filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - "#- ../certmanager", "#")).To(Succeed()) ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), "#- ../prometheus", "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - `#replacements:`, "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - certManagerTarget, "#")).To(Succeed()) ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "prometheus", "kustomization.yaml"), monitorTLSPatch, "#")).To(Succeed()) @@ -72,7 +65,6 @@ func GenerateV4(kbc *utils.TestContext) { ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), metricsCertReplaces, "#")).To(Succeed()) - uncommentKustomizeCoversion(kbc) } // GenerateV4WithoutMetrics implements a go/v4 plugin project defined by a TestContext. @@ -99,17 +91,9 @@ func GenerateV4WithoutMetrics(kbc *utils.TestContext) { Expect(err).NotTo(HaveOccurred(), "Failed to implement webhooks") scaffoldConversionWebhook(kbc) - - ExpectWithOffset(1, pluginutil.UncommentCode( - filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - "#- ../certmanager", "#")).To(Succeed()) ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), "#- ../prometheus", "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - `#replacements:`, "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - certManagerTarget, "#")).To(Succeed()) // Disable metrics ExpectWithOffset(1, pluginutil.CommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), @@ -117,8 +101,6 @@ func GenerateV4WithoutMetrics(kbc *utils.TestContext) { ExpectWithOffset(1, pluginutil.CommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), metricsTarget, "#")).To(Succeed()) - - uncommentKustomizeCoversion(kbc) } // GenerateV4WithoutMetrics implements a go/v4 plugin project defined by a TestContext. @@ -162,10 +144,6 @@ func GenerateV4WithNetworkPolicies(kbc *utils.TestContext) { Expect(err).NotTo(HaveOccurred(), "Failed to implement webhooks") scaffoldConversionWebhook(kbc) - - ExpectWithOffset(1, pluginutil.UncommentCode( - filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - "#- ../certmanager", "#")).To(Succeed()) ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), "#- ../prometheus", "#")).To(Succeed()) @@ -186,13 +164,6 @@ func GenerateV4WithNetworkPolicies(kbc *utils.TestContext) { ExpectWithOffset(1, pluginutil.UncommentCode( filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), "#- ../network-policy", "#")).To(Succeed()) - - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - `#replacements:`, "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - certManagerTarget, "#")).To(Succeed()) - - uncommentKustomizeCoversion(kbc) } // GenerateV4WithoutWebhooks implements a go/v4 plugin with APIs and enable Prometheus and CertManager @@ -242,139 +213,6 @@ const metricsTarget = `- path: manager_metrics_patch.yaml target: kind: Deployment` -const certManagerTarget = `# - source: # Uncomment the following block if you have any webhook -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # Name of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# 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 -# name: serving-cert -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) -# 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 -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# 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 -# fieldPath: .metadata.name -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true` - -const certNamespace = `# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: conversiontests.%s.%s -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true` - -const certName = `# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: conversiontests.%s.%s -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true` - // scaffoldConversionWebhook sets up conversion webhooks for testing the ConversionTest API func scaffoldConversionWebhook(kbc *utils.TestContext) { By("scaffolding conversion webhooks for testing ConversionTest v1 to v2 conversion") @@ -417,7 +255,7 @@ func scaffoldConversionWebhook(kbc *utils.TestContext) { By("implementing the size spec in v1") ExpectWithOffset(1, pluginutil.InsertCode( filepath.Join(kbc.Dir, "api", "v1", "conversiontest_types.go"), - "Foo string `json:\"foo,omitempty\"`", + "Foo *string `json:\"foo,omitempty\"`", "\n\tSize int `json:\"size,omitempty\"` // Number of desired instances", )).NotTo(HaveOccurred(), "failed to add size spec to conversiontest_types v1") @@ -425,28 +263,21 @@ func scaffoldConversionWebhook(kbc *utils.TestContext) { By("implementing the replicas spec in v2") ExpectWithOffset(1, pluginutil.InsertCode( filepath.Join(kbc.Dir, "api", "v2", "conversiontest_types.go"), - "Foo string `json:\"foo,omitempty\"`", + "Foo *string `json:\"foo,omitempty\"`", "\n\tReplicas int `json:\"replicas,omitempty\"` // Number of replicas", )).NotTo(HaveOccurred(), "failed to add replicas spec to conversiontest_conversion.go v2") err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"), - "// TODO(user): Implement conversion logic from v1 to v2", - `src.Spec.Size = dst.Spec.Replicas`) + "// dst.Spec.Size = src.Spec.Replicas", + "dst.Spec.Size = src.Spec.Replicas") Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v1 to v2") err = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, "api/v2/conversiontest_conversion.go"), - "// TODO(user): Implement conversion logic from v2 to v1", - `src.Spec.Replicas = dst.Spec.Size`) + "// dst.Spec.Replicas = src.Spec.Size", + "dst.Spec.Replicas = src.Spec.Size") Expect(err).NotTo(HaveOccurred(), "failed to implement conversion logic from v2 to v1") } -func uncommentKustomizeCoversion(kbc *utils.TestContext) { - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - fmt.Sprintf(certNamespace, kbc.Group, kbc.Domain), "#")).To(Succeed()) - ExpectWithOffset(1, pluginutil.UncommentCode(filepath.Join(kbc.Dir, "config", "default", "kustomization.yaml"), - fmt.Sprintf(certName, kbc.Group, kbc.Domain), "#")).To(Succeed()) -} - const monitorTLSPatch = `#patches: # - path: monitor_tls_patch.yaml # target: @@ -485,7 +316,7 @@ const metricsCertReplaces = `# - source: # Uncomment the following block to enab # delimiter: '.' # index: 0 # create: true -# + # - source: # kind: Service # version: v1 diff --git a/test/e2e/v4/plugin_cluster_test.go b/test/e2e/v4/plugin_cluster_test.go index b998b5fc574..2bab11205b5 100644 --- a/test/e2e/v4/plugin_cluster_test.go +++ b/test/e2e/v4/plugin_cluster_test.go @@ -57,12 +57,15 @@ var _ = Describe("kubebuilder", func() { }) AfterEach(func() { - By("By removing restricted namespace label") + By("removing restricted namespace label") _ = kbc.RemoveNamespaceLabelToEnforceRestricted() - By("clean up API objects created during the test") + By("undeploy the project") _ = kbc.Make("undeploy") + By("uninstalling the project") + _ = kbc.Make("uninstall") + By("removing controller image and working dir") kbc.Destroy() }) @@ -207,18 +210,6 @@ func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, isToUseHelmChart, Expect(err).NotTo(HaveOccurred()) if hasNetworkPolicies { - By("Checking for Calico pods") - var outputGet string - outputGet, err = kbc.Kubectl.Get( - false, - "pods", - "-n", "kube-system", - "-l", "k8s-app=calico-node", - "-o", "jsonpath={.items[*].status.phase}", - ) - Expect(err).NotTo(HaveOccurred(), "Failed to get Calico pods") - Expect(outputGet).To(ContainSubstring("Running"), "All Calico pods should be in Running state") - if hasMetrics { By("labeling the namespace to allow consume the metrics") Expect(kbc.Kubectl.Command("label", "namespaces", kbc.Kubectl.Namespace, @@ -401,26 +392,35 @@ func Run(kbc *utils.TestContext, hasWebhook, isToUseInstaller, isToUseHelmChart, By("modifying the ConversionTest CR sample to set `size` for conversion testing") conversionCRFile := filepath.Join("config", "samples", fmt.Sprintf("%s_v1_conversiontest.yaml", kbc.Group)) - conversionCRPath, err := filepath.Abs(filepath.Join(fmt.Sprintf("e2e-%s", kbc.TestSuffix), conversionCRFile)) - Expect(err).To(Not(HaveOccurred())) + conversionCRPath := filepath.Join(kbc.Dir, conversionCRFile) // Edit the file to include `size` in the spec field for v1 - f, err := os.OpenFile(conversionCRPath, os.O_APPEND|os.O_WRONLY, 0o644) - Expect(err).To(Not(HaveOccurred())) - defer func() { - err = f.Close() - Expect(err).To(Not(HaveOccurred())) - }() - _, err = f.WriteString("\nspec:\n size: 3") - Expect(err).To(Not(HaveOccurred())) + err = util.ReplaceInFile(conversionCRPath, "# TODO(user): Add fields here", `size: 3`) + Expect(err).NotTo(HaveOccurred(), "failed to replace spec in ConversionTest CR sample") // Apply the ConversionTest Custom Resource in v1 By("applying the modified ConversionTest CR in v1 for conversion") _, err = kbc.Kubectl.Apply(true, "-f", conversionCRPath) Expect(err).NotTo(HaveOccurred(), "failed to apply modified ConversionTest CR") - // TODO: Add validation to check the conversion - // the v2 should have spec.replicas == 3 + By("waiting for the ConversionTest CR to appear") + Eventually(func(g Gomega) { + _, err := kbc.Kubectl.Get(true, "conversiontest", "conversiontest-sample") + g.Expect(err).NotTo(HaveOccurred(), "expected the ConversionTest CR to exist") + }, time.Minute, time.Second).Should(Succeed()) + + By("validating that the converted resource in v2 has replicas == 3") + Eventually(func(g Gomega) { + out, err := kbc.Kubectl.Get( + true, + "conversiontest", "conversiontest-sample", + "-o", "jsonpath={.spec.replicas}", + ) + g.Expect(err).NotTo(HaveOccurred(), "failed to get converted resource in v2") + replicas, err := strconv.Atoi(out) + g.Expect(err).NotTo(HaveOccurred(), "replicas field is not an integer") + g.Expect(replicas).To(Equal(3), "expected replicas to be 3 after conversion") + }, time.Minute, time.Second).Should(Succeed()) if hasMetrics { By("validating conversion metrics to confirm conversion operations") @@ -512,6 +512,16 @@ func getMetricsOutput(kbc *utils.TestContext) string { Eventually(checkServiceEndpoint, 2*time.Minute, time.Second).Should(Succeed(), "Service endpoint should be ready") + // NOTE: On Kubernetes 1.33+, we've observed a delay before the metrics endpoint becomes available + // when using controller-runtime's WithAuthenticationAndAuthorization() with self-signed certificates. + // This delay appears to stem from Kubernetes itself, potentially due to changes in how it initializes + // service account tokens or handles TLS/service readiness. + // + // Without this delay, tests that curl the /metrics endpoint using a token can fail from k8s 1.33+. + // As a temporary workaround, we wait briefly before attempting to access metrics. + By("waiting briefly to ensure that the certs are provisioned and metrics are available") + time.Sleep(15 * time.Second) + By("creating a curl pod to access the metrics endpoint") cmdOpts := cmdOptsToCreateCurlPod(kbc, token) _, err = kbc.Kubectl.CommandInNamespace(cmdOpts...) @@ -528,6 +538,16 @@ func getMetricsOutput(kbc *utils.TestContext) string { } Eventually(verifyCurlUp, 240*time.Second, time.Second).Should(Succeed()) + By("validating that the correct ServiceAccount is being used") + saName := kbc.Kubectl.ServiceAccount + currentSAOutput, err := kbc.Kubectl.Get( + true, + "serviceaccount", saName, + "-o", "jsonpath={.metadata.name}", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to fetch the service account") + Expect(currentSAOutput).To(Equal(saName), "The ServiceAccount in use does not match the expected one") + By("validating that the metrics endpoint is serving as expected") getCurlLogs := func(g Gomega) { metricsOutput, err = kbc.Kubectl.Logs("curl") @@ -557,10 +577,10 @@ func metricsShouldBeUnavailable(kbc *utils.TestContext) { By("validating that the curl pod fail as expected") verifyCurlUp := func(g Gomega) { - status, err := kbc.Kubectl.Get( + status, errCurl := kbc.Kubectl.Get( true, "pods", "curl", "-o", "jsonpath={.status.phase}") - g.Expect(err).NotTo(HaveOccurred()) + g.Expect(errCurl).NotTo(HaveOccurred()) g.Expect(status).NotTo(Equal("Failed"), fmt.Sprintf("curl pod in %s status when should fail with an error", status)) } @@ -592,6 +612,7 @@ func cmdOptsToCreateCurlPod(kbc *utils.TestContext, token string) []string { "command": ["/bin/sh", "-c"], "args": ["curl -v -k -H 'Authorization: Bearer %s' https://e2e-%s-controller-manager-metrics-service.%s.svc.cluster.local:8443/metrics"], "securityContext": { + "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] @@ -603,7 +624,7 @@ func cmdOptsToCreateCurlPod(kbc *utils.TestContext, token string) []string { } } }], - "serviceAccount": "%s" + "serviceAccountName": "%s" } }`, token, kbc.TestSuffix, kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount), } @@ -612,7 +633,7 @@ func cmdOptsToCreateCurlPod(kbc *utils.TestContext, token string) []string { func removeCurlPod(kbc *utils.TestContext) { By("cleaning up the curl pod") - _, err := kbc.Kubectl.Delete(true, "pods/curl") + _, err := kbc.Kubectl.Delete(true, "pods/curl", "--grace-period=0", "--force") Expect(err).NotTo(HaveOccurred()) } @@ -621,17 +642,17 @@ func removeCurlPod(kbc *utils.TestContext) { // TokenRequest API in raw format in order to make it generic for all version of the k8s that // is currently being supported in kubebuilder test infra. // TokenRequest API returns the token in raw JWT format itself. There is no conversion required. -func serviceAccountToken(kbc *utils.TestContext) (out string, err error) { +func serviceAccountToken(kbc *utils.TestContext) (string, error) { + var out string + secretName := fmt.Sprintf("%s-token-request", kbc.Kubectl.ServiceAccount) tokenRequestFile := filepath.Join(kbc.Dir, secretName) - err = os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o755)) - if err != nil { - return out, err + if err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o755)); err != nil { + return out, fmt.Errorf("error creating token request file %s: %w", tokenRequestFile, err) } - var rawJSON string getToken := func(g Gomega) { // Output of this is already a valid JWT token. No need to covert this from base64 to string format - rawJSON, err = kbc.Kubectl.Command( + rawJSON, err := kbc.Kubectl.Command( "create", "--raw", fmt.Sprintf( "/api/v1/namespaces/%s/serviceaccounts/%s/token", @@ -650,5 +671,5 @@ func serviceAccountToken(kbc *utils.TestContext) (out string, err error) { } Eventually(getToken, time.Minute, time.Second).Should(Succeed()) - return out, err + return out, nil } diff --git a/test/features.sh b/test/features.sh new file mode 100755 index 00000000000..be3994429fe --- /dev/null +++ b/test/features.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright 2018 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. + +set -euo pipefail + +source "$(dirname "$0")/common.sh" + +header_text "Running e2e tests that do not require a cluster" + +build_kb +fetch_tools + +pushd . >/dev/null + +header_text "Running Grafana Plugin E2E tests" +go test "$(dirname "$0")/e2e/grafana" ${flags:-} -timeout 30m + +header_text "Running Alpha Generate Command E2E tests" +go test "$(dirname "$0")/e2e/alphagenerate" ${flags:-} -timeout 30m + +header_text "Running Alpha Update Command E2E tests" +go test "$(dirname "$0")/e2e/alphaupdate" ${flags:-} -timeout 30m + +popd >/dev/null diff --git a/testdata/project-v4-multigroup/.devcontainer/devcontainer.json b/testdata/project-v4-multigroup/.devcontainer/devcontainer.json index 0e0eed213f7..a3ab7541cb6 100644 --- a/testdata/project-v4-multigroup/.devcontainer/devcontainer.json +++ b/testdata/project-v4-multigroup/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Kubebuilder DevContainer", - "image": "docker.io/golang:1.23", + "image": "golang:1.24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} diff --git a/testdata/project-v4-multigroup/.github/workflows/lint.yml b/testdata/project-v4-multigroup/.github/workflows/lint.yml index 4951e3316c1..67ff2bf09c0 100644 --- a/testdata/project-v4-multigroup/.github/workflows/lint.yml +++ b/testdata/project-v4-multigroup/.github/workflows/lint.yml @@ -18,6 +18,6 @@ jobs: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.63.4 + version: v2.1.6 diff --git a/testdata/project-v4-multigroup/.github/workflows/test-e2e.yml b/testdata/project-v4-multigroup/.github/workflows/test-e2e.yml index b2eda8c3db0..68fd1ed5562 100644 --- a/testdata/project-v4-multigroup/.github/workflows/test-e2e.yml +++ b/testdata/project-v4-multigroup/.github/workflows/test-e2e.yml @@ -26,9 +26,6 @@ jobs: - name: Verify kind installation run: kind version - - name: Create kind cluster - run: kind create cluster - - name: Running Test e2e run: | go mod tidy diff --git a/testdata/project-v4-multigroup/.golangci.yml b/testdata/project-v4-multigroup/.golangci.yml index 6b297462382..e5b21b0f11c 100644 --- a/testdata/project-v4-multigroup/.golangci.yml +++ b/testdata/project-v4-multigroup/.golangci.yml @@ -1,33 +1,15 @@ +version: "2" run: - timeout: 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 + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -36,12 +18,35 @@ linters: - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/testdata/project-v4-multigroup/Dockerfile b/testdata/project-v4-multigroup/Dockerfile index 348b8372cd1..cb1b130fd9d 100644 --- a/testdata/project-v4-multigroup/Dockerfile +++ b/testdata/project-v4-multigroup/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/golang:1.23 AS builder +FROM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/testdata/project-v4-multigroup/Makefile b/testdata/project-v4-multigroup/Makefile index 3d2d6032f87..a7b7018d5de 100644 --- a/testdata/project-v4-multigroup/Makefile +++ b/testdata/project-v4-multigroup/Makefile @@ -65,17 +65,30 @@ test: manifests generate fmt vet setup-envtest ## Run tests. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # CertManager is installed by default; skip with: # - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +KIND_CLUSTER ?= project-v4-multigroup-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist @command -v $(KIND) >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ } - @$(KIND) get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -173,12 +186,12 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.1.6 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -206,7 +219,7 @@ $(ENVTEST): $(LOCALBIN) .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)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/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 diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index 1ad41336592..5823f59befb 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -2,6 +2,7 @@ # 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 +cliVersion: (devel) domain: testproject.org layout: - go.kubebuilder.io/v4 diff --git a/testdata/project-v4-multigroup/README.md b/testdata/project-v4-multigroup/README.md index 7a080601520..90e3634ccb2 100644 --- a/testdata/project-v4-multigroup/README.md +++ b/testdata/project-v4-multigroup/README.md @@ -7,7 +7,7 @@ ## Getting Started ### Prerequisites -- go version v1.23.0+ +- go version v1.24.0+ - docker version 17.03+. - kubectl version v1.11.3+. - Access to a Kubernetes v1.11.3+ cluster. diff --git a/testdata/project-v4-multigroup/api/crew/v1/captain_types.go b/testdata/project-v4-multigroup/api/crew/v1/captain_types.go index 84f051e64c0..7c7b0939774 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/captain_types.go +++ b/testdata/project-v4-multigroup/api/crew/v1/captain_types.go @@ -23,13 +23,16 @@ import ( // 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. -// CaptainSpec defines the desired state of Captain. +// CaptainSpec defines the desired state of Captain type CaptainSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Captain. Edit captain_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Captain. Edit captain_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // CaptainStatus defines the observed state of Captain. @@ -41,18 +44,26 @@ type CaptainStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Captain is the Schema for the captains API. +// Captain is the Schema for the captains API type Captain struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Captain + // +required + Spec CaptainSpec `json:"spec"` - Spec CaptainSpec `json:"spec,omitempty"` - Status CaptainStatus `json:"status,omitempty"` + // status defines the observed state of Captain + // +optional + Status CaptainStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// CaptainList contains a list of Captain. +// CaptainList contains a list of Captain type CaptainList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go index e7bfd293721..a3897c355b5 100644 --- a/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Captain) DeepCopyInto(out *Captain) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *CaptainList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec. diff --git a/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go index 5e4f42981d2..be3dab2e98c 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go +++ b/testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go @@ -23,13 +23,16 @@ import ( // 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. -// WordpressSpec defines the desired state of Wordpress. +// WordpressSpec defines the desired state of Wordpress type WordpressSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Wordpress. Edit wordpress_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Wordpress. Edit wordpress_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // WordpressStatus defines the observed state of Wordpress. @@ -40,21 +43,28 @@ type WordpressStatus struct { // +kubebuilder:object:root=true // +kubebuilder:storageversion -// +kubebuilder:conversion:hub // +kubebuilder:subresource:status -// Wordpress is the Schema for the wordpresses API. +// Wordpress is the Schema for the wordpresses API type Wordpress struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Wordpress + // +required + Spec WordpressSpec `json:"spec"` - Spec WordpressSpec `json:"spec,omitempty"` - Status WordpressStatus `json:"status,omitempty"` + // status defines the observed state of Wordpress + // +optional + Status WordpressStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// WordpressList contains a list of Wordpress. +// WordpressList contains a list of Wordpress type WordpressList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go index 246c890cd59..879f751c77b 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Wordpress) DeepCopyInto(out *Wordpress) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *WordpressList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec. diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/busybox_types.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/busybox_types.go index 1ebc3c331e2..58f1f644d41 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/busybox_types.go +++ b/testdata/project-v4-multigroup/api/example.com/v1alpha1/busybox_types.go @@ -27,28 +27,34 @@ import ( type BusyboxSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - - // Size defines the number of Busybox 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"` + + // size defines the number of Busybox instances + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 + // +optional + Size *int32 `json:"size,omitempty"` } // BusyboxStatus defines the observed state of Busybox type BusyboxStatus struct { - // Represents the observations of a Busybox's current state. - // Busybox.status.conditions.type are: "Available", "Progressing", and "Degraded" - // Busybox.status.conditions.status are one of True, False, Unknown. - // Busybox.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. - // Busybox.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"` + // For Kubernetes API conventions, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + // conditions represent the current state of the Busybox resource. + // Each condition has a unique type and reflects the status of a specific aspect of the resource. + // + // Standard condition types include: + // - "Available": the resource is fully functional + // - "Progressing": the resource is being created or updated + // - "Degraded": the resource failed to reach or maintain its desired state + // + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true @@ -56,11 +62,19 @@ type BusyboxStatus struct { // Busybox is the Schema for the busyboxes API type Busybox struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Busybox + // +required + Spec BusyboxSpec `json:"spec"` - Spec BusyboxSpec `json:"spec,omitempty"` - Status BusyboxStatus `json:"status,omitempty"` + // status defines the observed state of Busybox + // +optional + Status BusyboxStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_types.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_types.go index 7b9167c2dd5..e9a2b649dbb 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_types.go +++ b/testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_types.go @@ -27,31 +27,38 @@ import ( 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"` + // size defines the number of Memcached instances + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 + // +optional + Size *int32 `json:"size,omitempty"` + + // containerPort defines the port that will be used to init the container with the image + // +required + ContainerPort int32 `json:"containerPort"` } // 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"` + // For Kubernetes API conventions, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + // conditions represent the current state of the Memcached resource. + // Each condition has a unique type and reflects the status of a specific aspect of the resource. + // + // Standard condition types include: + // - "Available": the resource is fully functional + // - "Progressing": the resource is being created or updated + // - "Degraded": the resource failed to reach or maintain its desired state + // + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true @@ -59,11 +66,19 @@ type MemcachedStatus struct { // Memcached is the Schema for the memcacheds API type Memcached struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Memcached + // +required + Spec MemcachedSpec `json:"spec"` - Spec MemcachedSpec `json:"spec,omitempty"` - Status MemcachedStatus `json:"status,omitempty"` + // status defines the observed state of Memcached + // +optional + Status MemcachedStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true diff --git a/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go index 340cb1ad656..c927b380d0c 100644 --- a/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go @@ -30,7 +30,7 @@ func (in *Busybox) DeepCopyInto(out *Busybox) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -87,6 +87,11 @@ func (in *BusyboxList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) { *out = *in + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec. @@ -126,7 +131,7 @@ func (in *Memcached) DeepCopyInto(out *Memcached) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -183,6 +188,11 @@ func (in *MemcachedList) DeepCopyObject() runtime.Object { // 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 + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. diff --git a/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go index a2ca2aff5ff..03fd97ff392 100644 --- a/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go +++ b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go @@ -31,6 +31,12 @@ func (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v2 to v1 + // Example: Copying Spec fields + // dst.Spec.Size = src.Spec.Replicas + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } @@ -41,5 +47,11 @@ func (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v1 to v2 + // Example: Copying Spec fields + // dst.Spec.Replicas = src.Spec.Size + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } diff --git a/testdata/project-v4-multigroup/api/example.com/v2/wordpress_types.go b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_types.go index 332bca6de7d..1eb81a467ec 100644 --- a/testdata/project-v4-multigroup/api/example.com/v2/wordpress_types.go +++ b/testdata/project-v4-multigroup/api/example.com/v2/wordpress_types.go @@ -23,13 +23,16 @@ import ( // 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. -// WordpressSpec defines the desired state of Wordpress. +// WordpressSpec defines the desired state of Wordpress type WordpressSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Wordpress. Edit wordpress_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Wordpress. Edit wordpress_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // WordpressStatus defines the observed state of Wordpress. @@ -41,18 +44,26 @@ type WordpressStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Wordpress is the Schema for the wordpresses API. +// Wordpress is the Schema for the wordpresses API type Wordpress struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Wordpress + // +required + Spec WordpressSpec `json:"spec"` - Spec WordpressSpec `json:"spec,omitempty"` - Status WordpressStatus `json:"status,omitempty"` + // status defines the observed state of Wordpress + // +optional + Status WordpressStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// WordpressList contains a list of Wordpress. +// WordpressList contains a list of Wordpress type WordpressList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go index a21d7c776b7..c5c3a08a3ae 100644 --- a/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Wordpress) DeepCopyInto(out *Wordpress) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *WordpressList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec. diff --git a/testdata/project-v4-multigroup/api/fiz/v1/bar_types.go b/testdata/project-v4-multigroup/api/fiz/v1/bar_types.go index 969f081a383..1358797e934 100644 --- a/testdata/project-v4-multigroup/api/fiz/v1/bar_types.go +++ b/testdata/project-v4-multigroup/api/fiz/v1/bar_types.go @@ -23,13 +23,16 @@ import ( // 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. -// BarSpec defines the desired state of Bar. +// BarSpec defines the desired state of Bar type BarSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Bar. Edit bar_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Bar. Edit bar_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // BarStatus defines the observed state of Bar. @@ -41,18 +44,26 @@ type BarStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Bar is the Schema for the bars API. +// Bar is the Schema for the bars API type Bar struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Bar + // +required + Spec BarSpec `json:"spec"` - Spec BarSpec `json:"spec,omitempty"` - Status BarStatus `json:"status,omitempty"` + // status defines the observed state of Bar + // +optional + Status BarStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// BarList contains a list of Bar. +// BarList contains a list of Bar type BarList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/fiz/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/fiz/v1/zz_generated.deepcopy.go index 29af62a86c1..17448e17364 100644 --- a/testdata/project-v4-multigroup/api/fiz/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/fiz/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Bar) DeepCopyInto(out *Bar) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *BarList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BarSpec) DeepCopyInto(out *BarSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarSpec. diff --git a/testdata/project-v4-multigroup/api/foo.policy/v1/healthcheckpolicy_types.go b/testdata/project-v4-multigroup/api/foo.policy/v1/healthcheckpolicy_types.go index 02bbd92f366..d4eb3963d32 100644 --- a/testdata/project-v4-multigroup/api/foo.policy/v1/healthcheckpolicy_types.go +++ b/testdata/project-v4-multigroup/api/foo.policy/v1/healthcheckpolicy_types.go @@ -23,13 +23,16 @@ import ( // 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. -// HealthCheckPolicySpec defines the desired state of HealthCheckPolicy. +// HealthCheckPolicySpec defines the desired state of HealthCheckPolicy type HealthCheckPolicySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // HealthCheckPolicyStatus defines the observed state of HealthCheckPolicy. @@ -41,18 +44,26 @@ type HealthCheckPolicyStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// HealthCheckPolicy is the Schema for the healthcheckpolicies API. +// HealthCheckPolicy is the Schema for the healthcheckpolicies API type HealthCheckPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of HealthCheckPolicy + // +required + Spec HealthCheckPolicySpec `json:"spec"` - Spec HealthCheckPolicySpec `json:"spec,omitempty"` - Status HealthCheckPolicyStatus `json:"status,omitempty"` + // status defines the observed state of HealthCheckPolicy + // +optional + Status HealthCheckPolicyStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// HealthCheckPolicyList contains a list of HealthCheckPolicy. +// HealthCheckPolicyList contains a list of HealthCheckPolicy type HealthCheckPolicyList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/foo.policy/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/foo.policy/v1/zz_generated.deepcopy.go index ef1849ac407..51d06a70195 100644 --- a/testdata/project-v4-multigroup/api/foo.policy/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/foo.policy/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *HealthCheckPolicy) DeepCopyInto(out *HealthCheckPolicy) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *HealthCheckPolicyList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthCheckPolicySpec) DeepCopyInto(out *HealthCheckPolicySpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckPolicySpec. diff --git a/testdata/project-v4-multigroup/api/foo/v1/bar_types.go b/testdata/project-v4-multigroup/api/foo/v1/bar_types.go index 969f081a383..1358797e934 100644 --- a/testdata/project-v4-multigroup/api/foo/v1/bar_types.go +++ b/testdata/project-v4-multigroup/api/foo/v1/bar_types.go @@ -23,13 +23,16 @@ import ( // 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. -// BarSpec defines the desired state of Bar. +// BarSpec defines the desired state of Bar type BarSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Bar. Edit bar_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Bar. Edit bar_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // BarStatus defines the observed state of Bar. @@ -41,18 +44,26 @@ type BarStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Bar is the Schema for the bars API. +// Bar is the Schema for the bars API type Bar struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Bar + // +required + Spec BarSpec `json:"spec"` - Spec BarSpec `json:"spec,omitempty"` - Status BarStatus `json:"status,omitempty"` + // status defines the observed state of Bar + // +optional + Status BarStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// BarList contains a list of Bar. +// BarList contains a list of Bar type BarList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/foo/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/foo/v1/zz_generated.deepcopy.go index 29af62a86c1..17448e17364 100644 --- a/testdata/project-v4-multigroup/api/foo/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/foo/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Bar) DeepCopyInto(out *Bar) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *BarList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BarSpec) DeepCopyInto(out *BarSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarSpec. diff --git a/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/kraken_types.go b/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/kraken_types.go index e59545fb595..4eef92901fa 100644 --- a/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/kraken_types.go +++ b/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/kraken_types.go @@ -23,13 +23,16 @@ import ( // 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. -// KrakenSpec defines the desired state of Kraken. +// KrakenSpec defines the desired state of Kraken type KrakenSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Kraken. Edit kraken_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Kraken. Edit kraken_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // KrakenStatus defines the observed state of Kraken. @@ -41,18 +44,26 @@ type KrakenStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Kraken is the Schema for the krakens API. +// Kraken is the Schema for the krakens API type Kraken struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Kraken + // +required + Spec KrakenSpec `json:"spec"` - Spec KrakenSpec `json:"spec,omitempty"` - Status KrakenStatus `json:"status,omitempty"` + // status defines the observed state of Kraken + // +optional + Status KrakenStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// KrakenList contains a list of Kraken. +// KrakenList contains a list of Kraken type KrakenList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/zz_generated.deepcopy.go index 49b80487277..b6e14674825 100644 --- a/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/sea-creatures/v1beta1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Kraken) DeepCopyInto(out *Kraken) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *KrakenList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KrakenSpec) DeepCopyInto(out *KrakenSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KrakenSpec. diff --git a/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/leviathan_types.go b/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/leviathan_types.go index 494df747b47..36c8c635c09 100644 --- a/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/leviathan_types.go +++ b/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/leviathan_types.go @@ -23,13 +23,16 @@ import ( // 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. -// LeviathanSpec defines the desired state of Leviathan. +// LeviathanSpec defines the desired state of Leviathan type LeviathanSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Leviathan. Edit leviathan_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Leviathan. Edit leviathan_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // LeviathanStatus defines the observed state of Leviathan. @@ -41,18 +44,26 @@ type LeviathanStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Leviathan is the Schema for the leviathans API. +// Leviathan is the Schema for the leviathans API type Leviathan struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Leviathan + // +required + Spec LeviathanSpec `json:"spec"` - Spec LeviathanSpec `json:"spec,omitempty"` - Status LeviathanStatus `json:"status,omitempty"` + // status defines the observed state of Leviathan + // +optional + Status LeviathanStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// LeviathanList contains a list of Leviathan. +// LeviathanList contains a list of Leviathan type LeviathanList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/zz_generated.deepcopy.go index 3f0c92ff80c..0f28eda0793 100644 --- a/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/sea-creatures/v1beta2/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Leviathan) DeepCopyInto(out *Leviathan) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *LeviathanList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LeviathanSpec) DeepCopyInto(out *LeviathanSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeviathanSpec. diff --git a/testdata/project-v4-multigroup/api/ship/v1/destroyer_types.go b/testdata/project-v4-multigroup/api/ship/v1/destroyer_types.go index f0788d71d6d..f71c0df0077 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/destroyer_types.go +++ b/testdata/project-v4-multigroup/api/ship/v1/destroyer_types.go @@ -23,13 +23,16 @@ import ( // 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. -// DestroyerSpec defines the desired state of Destroyer. +// DestroyerSpec defines the desired state of Destroyer type DestroyerSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Destroyer. Edit destroyer_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Destroyer. Edit destroyer_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // DestroyerStatus defines the observed state of Destroyer. @@ -42,18 +45,26 @@ type DestroyerStatus struct { // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster -// Destroyer is the Schema for the destroyers API. +// Destroyer is the Schema for the destroyers API type Destroyer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Destroyer + // +required + Spec DestroyerSpec `json:"spec"` - Spec DestroyerSpec `json:"spec,omitempty"` - Status DestroyerStatus `json:"status,omitempty"` + // status defines the observed state of Destroyer + // +optional + Status DestroyerStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// DestroyerList contains a list of Destroyer. +// DestroyerList contains a list of Destroyer type DestroyerList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go index 10ca630cb3b..f91e16e2885 100644 --- a/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Destroyer) DeepCopyInto(out *Destroyer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *DestroyerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestroyerSpec) DeepCopyInto(out *DestroyerSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerSpec. diff --git a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_types.go b/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_types.go index 0e5459b2632..a16f94e6f18 100644 --- a/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_types.go +++ b/testdata/project-v4-multigroup/api/ship/v1beta1/frigate_types.go @@ -23,13 +23,16 @@ import ( // 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. -// FrigateSpec defines the desired state of Frigate. +// FrigateSpec defines the desired state of Frigate type FrigateSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Frigate. Edit frigate_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Frigate. Edit frigate_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // FrigateStatus defines the observed state of Frigate. @@ -41,18 +44,26 @@ type FrigateStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Frigate is the Schema for the frigates API. +// Frigate is the Schema for the frigates API type Frigate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Frigate + // +required + Spec FrigateSpec `json:"spec"` - Spec FrigateSpec `json:"spec,omitempty"` - Status FrigateStatus `json:"status,omitempty"` + // status defines the observed state of Frigate + // +optional + Status FrigateStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// FrigateList contains a list of Frigate. +// FrigateList contains a list of Frigate type FrigateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/ship/v1beta1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/ship/v1beta1/zz_generated.deepcopy.go index fe6b7434b49..161034b8e2d 100644 --- a/testdata/project-v4-multigroup/api/ship/v1beta1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/ship/v1beta1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Frigate) DeepCopyInto(out *Frigate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *FrigateList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FrigateSpec) DeepCopyInto(out *FrigateSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrigateSpec. diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_types.go b/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_types.go index ea1594430df..14db9d66d69 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_types.go +++ b/testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_types.go @@ -23,13 +23,16 @@ import ( // 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. -// CruiserSpec defines the desired state of Cruiser. +// CruiserSpec defines the desired state of Cruiser type CruiserSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Cruiser. Edit cruiser_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Cruiser. Edit cruiser_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // CruiserStatus defines the observed state of Cruiser. @@ -42,18 +45,26 @@ type CruiserStatus struct { // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster -// Cruiser is the Schema for the cruisers API. +// Cruiser is the Schema for the cruisers API type Cruiser struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Cruiser + // +required + Spec CruiserSpec `json:"spec"` - Spec CruiserSpec `json:"spec,omitempty"` - Status CruiserStatus `json:"status,omitempty"` + // status defines the observed state of Cruiser + // +optional + Status CruiserStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// CruiserList contains a list of Cruiser. +// CruiserList contains a list of Cruiser type CruiserList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go index 74fad083f2f..899e1ed0e34 100644 --- a/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Cruiser) DeepCopyInto(out *Cruiser) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *CruiserList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CruiserSpec) DeepCopyInto(out *CruiserSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserSpec. diff --git a/testdata/project-v4-multigroup/cmd/main.go b/testdata/project-v4-multigroup/cmd/main.go index cb54ea82df8..4e230025f84 100644 --- a/testdata/project-v4-multigroup/cmd/main.go +++ b/testdata/project-v4-multigroup/cmd/main.go @@ -176,7 +176,7 @@ func main() { // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server // - https://book.kubebuilder.io/reference/metrics.html metricsServerOptions := metricsserver.Options{ BindAddress: metricsAddr, @@ -188,7 +188,7 @@ func main() { // FilterProvider is used to protect the metrics endpoint with authn/authz. // These configurations ensure that only authorized users and service accounts // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } @@ -243,7 +243,7 @@ func main() { os.Exit(1) } - if err = (&crewcontroller.CaptainReconciler{ + if err := (&crewcontroller.CaptainReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -252,19 +252,19 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcrewv1.SetupCaptainWebhookWithManager(mgr); err != nil { + if err := webhookcrewv1.SetupCaptainWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Captain") os.Exit(1) } } - if err = (&shipcontroller.FrigateReconciler{ + if err := (&shipcontroller.FrigateReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Frigate") os.Exit(1) } - if err = (&shipcontroller.DestroyerReconciler{ + if err := (&shipcontroller.DestroyerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -273,12 +273,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookshipv1.SetupDestroyerWebhookWithManager(mgr); err != nil { + if err := webhookshipv1.SetupDestroyerWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Destroyer") os.Exit(1) } } - if err = (&shipcontroller.CruiserReconciler{ + if err := (&shipcontroller.CruiserReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -287,54 +287,54 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookshipv2alpha1.SetupCruiserWebhookWithManager(mgr); err != nil { + if err := webhookshipv2alpha1.SetupCruiserWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Cruiser") os.Exit(1) } } - if err = (&seacreaturescontroller.KrakenReconciler{ + if err := (&seacreaturescontroller.KrakenReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Kraken") os.Exit(1) } - if err = (&seacreaturescontroller.LeviathanReconciler{ + if err := (&seacreaturescontroller.LeviathanReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Leviathan") os.Exit(1) } - if err = (&foopolicycontroller.HealthCheckPolicyReconciler{ + if err := (&foopolicycontroller.HealthCheckPolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "HealthCheckPolicy") os.Exit(1) } - if err = (&appscontroller.DeploymentReconciler{ + if err := (&appscontroller.DeploymentReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } - if err = (&foocontroller.BarReconciler{ + if err := (&foocontroller.BarReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Bar") os.Exit(1) } - if err = (&fizcontroller.BarReconciler{ + if err := (&fizcontroller.BarReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Bar") os.Exit(1) } - if err = (&certmanagercontroller.CertificateReconciler{ + if err := (&certmanagercontroller.CertificateReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -343,26 +343,26 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcertmanagerv1.SetupIssuerWebhookWithManager(mgr); err != nil { + if err := webhookcertmanagerv1.SetupIssuerWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Issuer") os.Exit(1) } } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil { + if err := webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Pod") os.Exit(1) } } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookappsv1.SetupDeploymentWebhookWithManager(mgr); err != nil { + if err := webhookappsv1.SetupDeploymentWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Deployment") os.Exit(1) } } - if err = (&examplecomcontroller.MemcachedReconciler{ + if err := (&examplecomcontroller.MemcachedReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("memcached-controller"), @@ -370,7 +370,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Memcached") os.Exit(1) } - if err = (&examplecomcontroller.BusyboxReconciler{ + if err := (&examplecomcontroller.BusyboxReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("busybox-controller"), @@ -380,12 +380,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { + if err := webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") os.Exit(1) } } - if err = (&examplecomcontroller.WordpressReconciler{ + if err := (&examplecomcontroller.WordpressReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -394,7 +394,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookexamplecomv1.SetupWordpressWebhookWithManager(mgr); err != nil { + if err := webhookexamplecomv1.SetupWordpressWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Wordpress") os.Exit(1) } diff --git a/testdata/project-v4-multigroup/config/crd/bases/crew.testproject.org_captains.yaml b/testdata/project-v4-multigroup/config/crd/bases/crew.testproject.org_captains.yaml index c8cf3d723a0..438c614dd33 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/crew.testproject.org_captains.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/crew.testproject.org_captains.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: captains.crew.testproject.org spec: group: crew.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Captain is the Schema for the captains API. + description: Captain is the Schema for the captains API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: CaptainSpec defines the desired state of Captain. + description: spec defines the desired state of Captain properties: foo: - description: Foo is an example field of Captain. Edit captain_types.go + description: foo is an example field of Captain. Edit captain_types.go to remove/update type: string type: object status: - description: CaptainStatus defines the observed state of Captain. + description: status defines the observed state of Captain type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_busyboxes.yaml b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_busyboxes.yaml index f22e8ee9ba9..11fb23ee219 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_busyboxes.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_busyboxes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: busyboxes.example.com.testproject.org spec: group: example.com.testproject.org @@ -37,22 +37,29 @@ spec: metadata: type: object spec: - description: BusyboxSpec defines the desired state of Busybox + description: spec defines the desired state of Busybox properties: size: - description: |- - Size defines the number of Busybox instances - The following markers will use OpenAPI v3 schema to validate the value - More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + default: 1 + description: size defines the number of Busybox instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer type: object status: - description: BusyboxStatus defines the observed state of Busybox + description: status defines the observed state of Busybox properties: conditions: + description: |- + conditions represent the current state of the Busybox resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -108,7 +115,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_memcacheds.yaml b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_memcacheds.yaml index 2abcfada046..d3bbbe38c34 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_memcacheds.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_memcacheds.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: memcacheds.example.com.testproject.org spec: group: example.com.testproject.org @@ -37,27 +37,36 @@ spec: metadata: type: object spec: - description: MemcachedSpec defines the desired state of Memcached + description: spec defines the desired state of Memcached properties: containerPort: - description: Port defines the port that will be used to init the container - with the image + description: containerPort 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 + default: 1 + description: size defines the number of Memcached instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer + required: + - containerPort type: object status: - description: MemcachedStatus defines the observed state of Memcached + description: status defines the observed state of Memcached properties: conditions: + description: |- + conditions represent the current state of the Memcached resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -113,7 +122,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_wordpresses.yaml b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_wordpresses.yaml index 12280a20543..5a9493bc6c3 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_wordpresses.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_wordpresses.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: wordpresses.example.com.testproject.org spec: group: example.com.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: true @@ -55,7 +57,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -75,16 +77,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: false diff --git a/testdata/project-v4-multigroup/config/crd/bases/fiz.testproject.org_bars.yaml b/testdata/project-v4-multigroup/config/crd/bases/fiz.testproject.org_bars.yaml index c78088245f9..0ef199e9349 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/fiz.testproject.org_bars.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/fiz.testproject.org_bars.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: bars.fiz.testproject.org spec: group: fiz.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Bar is the Schema for the bars API. + description: Bar is the Schema for the bars API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: BarSpec defines the desired state of Bar. + description: spec defines the desired state of Bar properties: foo: - description: Foo is an example field of Bar. Edit bar_types.go to + description: foo is an example field of Bar. Edit bar_types.go to remove/update type: string type: object status: - description: BarStatus defines the observed state of Bar. + description: status defines the observed state of Bar type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/foo.policy.testproject.org_healthcheckpolicies.yaml b/testdata/project-v4-multigroup/config/crd/bases/foo.policy.testproject.org_healthcheckpolicies.yaml index efb036a07d3..9d31d13fd32 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/foo.policy.testproject.org_healthcheckpolicies.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/foo.policy.testproject.org_healthcheckpolicies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: healthcheckpolicies.foo.policy.testproject.org spec: group: foo.policy.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: HealthCheckPolicy is the Schema for the healthcheckpolicies API. + description: HealthCheckPolicy is the Schema for the healthcheckpolicies API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: HealthCheckPolicySpec defines the desired state of HealthCheckPolicy. + description: spec defines the desired state of HealthCheckPolicy properties: foo: - description: Foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go + description: foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go to remove/update type: string type: object status: - description: HealthCheckPolicyStatus defines the observed state of HealthCheckPolicy. + description: status defines the observed state of HealthCheckPolicy type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/foo.testproject.org_bars.yaml b/testdata/project-v4-multigroup/config/crd/bases/foo.testproject.org_bars.yaml index 55906fd92aa..18c162bf945 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/foo.testproject.org_bars.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/foo.testproject.org_bars.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: bars.foo.testproject.org spec: group: foo.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Bar is the Schema for the bars API. + description: Bar is the Schema for the bars API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: BarSpec defines the desired state of Bar. + description: spec defines the desired state of Bar properties: foo: - description: Foo is an example field of Bar. Edit bar_types.go to + description: foo is an example field of Bar. Edit bar_types.go to remove/update type: string type: object status: - description: BarStatus defines the observed state of Bar. + description: status defines the observed state of Bar type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_krakens.yaml b/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_krakens.yaml index b4d5eec6044..1ed25995edc 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_krakens.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_krakens.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: krakens.sea-creatures.testproject.org spec: group: sea-creatures.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1beta1 schema: openAPIV3Schema: - description: Kraken is the Schema for the krakens API. + description: Kraken is the Schema for the krakens API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: KrakenSpec defines the desired state of Kraken. + description: spec defines the desired state of Kraken properties: foo: - description: Foo is an example field of Kraken. Edit kraken_types.go + description: foo is an example field of Kraken. Edit kraken_types.go to remove/update type: string type: object status: - description: KrakenStatus defines the observed state of Kraken. + description: status defines the observed state of Kraken type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_leviathans.yaml b/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_leviathans.yaml index 9e2313c6f6f..d39ee67e76e 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_leviathans.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_leviathans.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: leviathans.sea-creatures.testproject.org spec: group: sea-creatures.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1beta2 schema: openAPIV3Schema: - description: Leviathan is the Schema for the leviathans API. + description: Leviathan is the Schema for the leviathans API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: LeviathanSpec defines the desired state of Leviathan. + description: spec defines the desired state of Leviathan properties: foo: - description: Foo is an example field of Leviathan. Edit leviathan_types.go + description: foo is an example field of Leviathan. Edit leviathan_types.go to remove/update type: string type: object status: - description: LeviathanStatus defines the observed state of Leviathan. + description: status defines the observed state of Leviathan type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_cruisers.yaml b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_cruisers.yaml index adc790d767d..7192c4dd3c4 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_cruisers.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_cruisers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: cruisers.ship.testproject.org spec: group: ship.testproject.org @@ -17,7 +17,7 @@ spec: - name: v2alpha1 schema: openAPIV3Schema: - description: Cruiser is the Schema for the cruisers API. + description: Cruiser is the Schema for the cruisers API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: CruiserSpec defines the desired state of Cruiser. + description: spec defines the desired state of Cruiser properties: foo: - description: Foo is an example field of Cruiser. Edit cruiser_types.go + description: foo is an example field of Cruiser. Edit cruiser_types.go to remove/update type: string type: object status: - description: CruiserStatus defines the observed state of Cruiser. + description: status defines the observed state of Cruiser type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_destroyers.yaml b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_destroyers.yaml index afc86ad7a4d..5bf45965552 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_destroyers.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_destroyers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: destroyers.ship.testproject.org spec: group: ship.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Destroyer is the Schema for the destroyers API. + description: Destroyer is the Schema for the destroyers API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: DestroyerSpec defines the desired state of Destroyer. + description: spec defines the desired state of Destroyer properties: foo: - description: Foo is an example field of Destroyer. Edit destroyer_types.go + description: foo is an example field of Destroyer. Edit destroyer_types.go to remove/update type: string type: object status: - description: DestroyerStatus defines the observed state of Destroyer. + description: status defines the observed state of Destroyer type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_frigates.yaml b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_frigates.yaml index b7def3fc303..f4d1364d53a 100644 --- a/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_frigates.yaml +++ b/testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_frigates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: frigates.ship.testproject.org spec: group: ship.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1beta1 schema: openAPIV3Schema: - description: Frigate is the Schema for the frigates API. + description: Frigate is the Schema for the frigates API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: FrigateSpec defines the desired state of Frigate. + description: spec defines the desired state of Frigate properties: foo: - description: Foo is an example field of Frigate. Edit frigate_types.go + description: foo is an example field of Frigate. Edit frigate_types.go to remove/update type: string type: object status: - description: FrigateStatus defines the observed state of Frigate. + description: status defines the observed state of Frigate type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-multigroup/config/default/kustomization.yaml b/testdata/project-v4-multigroup/config/default/kustomization.yaml index 3fdc4dd6c70..2388adcaeda 100644 --- a/testdata/project-v4-multigroup/config/default/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/default/kustomization.yaml @@ -22,7 +22,7 @@ resources: # crd/kustomization.yaml - ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus # [METRICS] Expose the controller manager metrics service. @@ -56,7 +56,7 @@ patches: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: +replacements: # - source: # Uncomment the following block to enable certificates for metrics # kind: Service # version: v1 @@ -86,7 +86,7 @@ patches: # delimiter: '.' # index: 0 # create: true -# + # - source: # kind: Service # version: v1 @@ -116,137 +116,137 @@ patches: # delimiter: '.' # index: 1 # create: true -# -# - source: # Uncomment the following block if you have any webhook -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # Name of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# 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 -# name: serving-cert -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) -# 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 -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# 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 -# fieldPath: .metadata.name -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: wordpresses.example.com.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true + + - source: # Uncomment the following block if you have any webhook + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name # Name of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + 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 + name: serving-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true + + - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) + 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 + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + + - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.namespace # Namespace of the certificate CR + targets: + - select: + kind: MutatingWebhookConfiguration + 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 + fieldPath: .metadata.name + targets: + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + + - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.namespace # Namespace of the certificate CR + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: wordpresses.example.com.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionns -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: wordpresses.example.com.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: wordpresses.example.com.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionname diff --git a/testdata/project-v4-multigroup/config/manager/manager.yaml b/testdata/project-v4-multigroup/config/manager/manager.yaml index c55eead00d9..6595f5d2236 100644 --- a/testdata/project-v4-multigroup/config/manager/manager.yaml +++ b/testdata/project-v4-multigroup/config/manager/manager.yaml @@ -72,6 +72,7 @@ spec: value: memcached:1.6.26-alpine3.19 ports: [] securityContext: + readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: diff --git a/testdata/project-v4-multigroup/config/rbac/kustomization.yaml b/testdata/project-v4-multigroup/config/rbac/kustomization.yaml index 7bdeb3156c2..b5059bfb5c6 100644 --- a/testdata/project-v4-multigroup/config/rbac/kustomization.yaml +++ b/testdata/project-v4-multigroup/config/rbac/kustomization.yaml @@ -20,7 +20,7 @@ resources: - metrics_reader_role.yaml # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are -# not used by the {{ .ProjectName }} itself. You can comment the following lines +# not used by the project-v4-multigroup itself. You can comment the following lines # if you do not want those helpers be installed with your Project. - example.com_wordpress_admin_role.yaml - example.com_wordpress_editor_role.yaml diff --git a/testdata/project-v4-multigroup/dist/install.yaml b/testdata/project-v4-multigroup/dist/install.yaml index 074d8843e8f..be91024fcd8 100644 --- a/testdata/project-v4-multigroup/dist/install.yaml +++ b/testdata/project-v4-multigroup/dist/install.yaml @@ -11,7 +11,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: bars.fiz.testproject.org spec: group: fiz.testproject.org @@ -25,7 +25,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Bar is the Schema for the bars API. + description: Bar is the Schema for the bars API properties: apiVersion: description: |- @@ -45,16 +45,18 @@ spec: metadata: type: object spec: - description: BarSpec defines the desired state of Bar. + description: spec defines the desired state of Bar properties: foo: - description: Foo is an example field of Bar. Edit bar_types.go to + description: foo is an example field of Bar. Edit bar_types.go to remove/update type: string type: object status: - description: BarStatus defines the observed state of Bar. + description: status defines the observed state of Bar type: object + required: + - spec type: object served: true storage: true @@ -65,7 +67,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: bars.foo.testproject.org spec: group: foo.testproject.org @@ -79,7 +81,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Bar is the Schema for the bars API. + description: Bar is the Schema for the bars API properties: apiVersion: description: |- @@ -99,16 +101,18 @@ spec: metadata: type: object spec: - description: BarSpec defines the desired state of Bar. + description: spec defines the desired state of Bar properties: foo: - description: Foo is an example field of Bar. Edit bar_types.go to + description: foo is an example field of Bar. Edit bar_types.go to remove/update type: string type: object status: - description: BarStatus defines the observed state of Bar. + description: status defines the observed state of Bar type: object + required: + - spec type: object served: true storage: true @@ -119,7 +123,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: busyboxes.example.com.testproject.org spec: group: example.com.testproject.org @@ -153,22 +157,29 @@ spec: metadata: type: object spec: - description: BusyboxSpec defines the desired state of Busybox + description: spec defines the desired state of Busybox properties: size: - description: |- - Size defines the number of Busybox instances - The following markers will use OpenAPI v3 schema to validate the value - More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + default: 1 + description: size defines the number of Busybox instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer type: object status: - description: BusyboxStatus defines the observed state of Busybox + description: status defines the observed state of Busybox properties: conditions: + description: |- + conditions represent the current state of the Busybox resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -224,7 +235,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true @@ -235,7 +251,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: captains.crew.testproject.org spec: group: crew.testproject.org @@ -249,7 +265,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Captain is the Schema for the captains API. + description: Captain is the Schema for the captains API properties: apiVersion: description: |- @@ -269,16 +285,18 @@ spec: metadata: type: object spec: - description: CaptainSpec defines the desired state of Captain. + description: spec defines the desired state of Captain properties: foo: - description: Foo is an example field of Captain. Edit captain_types.go + description: foo is an example field of Captain. Edit captain_types.go to remove/update type: string type: object status: - description: CaptainStatus defines the observed state of Captain. + description: status defines the observed state of Captain type: object + required: + - spec type: object served: true storage: true @@ -289,7 +307,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: cruisers.ship.testproject.org spec: group: ship.testproject.org @@ -303,7 +321,7 @@ spec: - name: v2alpha1 schema: openAPIV3Schema: - description: Cruiser is the Schema for the cruisers API. + description: Cruiser is the Schema for the cruisers API properties: apiVersion: description: |- @@ -323,16 +341,18 @@ spec: metadata: type: object spec: - description: CruiserSpec defines the desired state of Cruiser. + description: spec defines the desired state of Cruiser properties: foo: - description: Foo is an example field of Cruiser. Edit cruiser_types.go + description: foo is an example field of Cruiser. Edit cruiser_types.go to remove/update type: string type: object status: - description: CruiserStatus defines the observed state of Cruiser. + description: status defines the observed state of Cruiser type: object + required: + - spec type: object served: true storage: true @@ -343,7 +363,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: destroyers.ship.testproject.org spec: group: ship.testproject.org @@ -357,7 +377,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Destroyer is the Schema for the destroyers API. + description: Destroyer is the Schema for the destroyers API properties: apiVersion: description: |- @@ -377,16 +397,18 @@ spec: metadata: type: object spec: - description: DestroyerSpec defines the desired state of Destroyer. + description: spec defines the desired state of Destroyer properties: foo: - description: Foo is an example field of Destroyer. Edit destroyer_types.go + description: foo is an example field of Destroyer. Edit destroyer_types.go to remove/update type: string type: object status: - description: DestroyerStatus defines the observed state of Destroyer. + description: status defines the observed state of Destroyer type: object + required: + - spec type: object served: true storage: true @@ -397,7 +419,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: frigates.ship.testproject.org spec: group: ship.testproject.org @@ -411,7 +433,7 @@ spec: - name: v1beta1 schema: openAPIV3Schema: - description: Frigate is the Schema for the frigates API. + description: Frigate is the Schema for the frigates API properties: apiVersion: description: |- @@ -431,16 +453,18 @@ spec: metadata: type: object spec: - description: FrigateSpec defines the desired state of Frigate. + description: spec defines the desired state of Frigate properties: foo: - description: Foo is an example field of Frigate. Edit frigate_types.go + description: foo is an example field of Frigate. Edit frigate_types.go to remove/update type: string type: object status: - description: FrigateStatus defines the observed state of Frigate. + description: status defines the observed state of Frigate type: object + required: + - spec type: object served: true storage: true @@ -451,7 +475,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: healthcheckpolicies.foo.policy.testproject.org spec: group: foo.policy.testproject.org @@ -465,7 +489,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: HealthCheckPolicy is the Schema for the healthcheckpolicies API. + description: HealthCheckPolicy is the Schema for the healthcheckpolicies API properties: apiVersion: description: |- @@ -485,16 +509,18 @@ spec: metadata: type: object spec: - description: HealthCheckPolicySpec defines the desired state of HealthCheckPolicy. + description: spec defines the desired state of HealthCheckPolicy properties: foo: - description: Foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go + description: foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go to remove/update type: string type: object status: - description: HealthCheckPolicyStatus defines the observed state of HealthCheckPolicy. + description: status defines the observed state of HealthCheckPolicy type: object + required: + - spec type: object served: true storage: true @@ -505,7 +531,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: krakens.sea-creatures.testproject.org spec: group: sea-creatures.testproject.org @@ -519,7 +545,7 @@ spec: - name: v1beta1 schema: openAPIV3Schema: - description: Kraken is the Schema for the krakens API. + description: Kraken is the Schema for the krakens API properties: apiVersion: description: |- @@ -539,16 +565,18 @@ spec: metadata: type: object spec: - description: KrakenSpec defines the desired state of Kraken. + description: spec defines the desired state of Kraken properties: foo: - description: Foo is an example field of Kraken. Edit kraken_types.go + description: foo is an example field of Kraken. Edit kraken_types.go to remove/update type: string type: object status: - description: KrakenStatus defines the observed state of Kraken. + description: status defines the observed state of Kraken type: object + required: + - spec type: object served: true storage: true @@ -559,7 +587,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: leviathans.sea-creatures.testproject.org spec: group: sea-creatures.testproject.org @@ -573,7 +601,7 @@ spec: - name: v1beta2 schema: openAPIV3Schema: - description: Leviathan is the Schema for the leviathans API. + description: Leviathan is the Schema for the leviathans API properties: apiVersion: description: |- @@ -593,16 +621,18 @@ spec: metadata: type: object spec: - description: LeviathanSpec defines the desired state of Leviathan. + description: spec defines the desired state of Leviathan properties: foo: - description: Foo is an example field of Leviathan. Edit leviathan_types.go + description: foo is an example field of Leviathan. Edit leviathan_types.go to remove/update type: string type: object status: - description: LeviathanStatus defines the observed state of Leviathan. + description: status defines the observed state of Leviathan type: object + required: + - spec type: object served: true storage: true @@ -613,7 +643,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: memcacheds.example.com.testproject.org spec: group: example.com.testproject.org @@ -647,27 +677,36 @@ spec: metadata: type: object spec: - description: MemcachedSpec defines the desired state of Memcached + description: spec defines the desired state of Memcached properties: containerPort: - description: Port defines the port that will be used to init the container - with the image + description: containerPort 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 + default: 1 + description: size defines the number of Memcached instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer + required: + - containerPort type: object status: - description: MemcachedStatus defines the observed state of Memcached + description: status defines the observed state of Memcached properties: conditions: + description: |- + conditions represent the current state of the Memcached resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -723,7 +762,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true @@ -734,7 +778,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert + controller-gen.kubebuilder.io/version: v0.18.0 name: wordpresses.example.com.testproject.org spec: conversion: @@ -758,7 +803,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -778,16 +823,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: true @@ -796,7 +843,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -816,16 +863,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: false @@ -2157,6 +2206,7 @@ spec: capabilities: drop: - ALL + readOnlyRootFilesystem: true volumeMounts: - mountPath: /tmp/k8s-webhook-server/serving-certs name: webhook-certs @@ -2172,9 +2222,56 @@ spec: secret: secretName: webhook-server-cert --- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-multigroup + name: project-v4-multigroup-metrics-certs + namespace: project-v4-multigroup-system +spec: + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-multigroup-selfsigned-issuer + secretName: metrics-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-multigroup + name: project-v4-multigroup-serving-cert + namespace: project-v4-multigroup-system +spec: + dnsNames: + - project-v4-multigroup-webhook-service.project-v4-multigroup-system.svc + - project-v4-multigroup-webhook-service.project-v4-multigroup-system.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-multigroup-selfsigned-issuer + secretName: webhook-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-multigroup + name: project-v4-multigroup-selfsigned-issuer + namespace: project-v4-multigroup-system +spec: + selfSigned: {} +--- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + annotations: + cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert name: project-v4-multigroup-mutating-webhook-configuration webhooks: - admissionReviewVersions: @@ -2261,6 +2358,8 @@ webhooks: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: + annotations: + cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert name: project-v4-multigroup-validating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/testdata/project-v4-multigroup/go.mod b/testdata/project-v4-multigroup/go.mod index 166e3cc7265..f1d2972edc9 100644 --- a/testdata/project-v4-multigroup/go.mod +++ b/testdata/project-v4-multigroup/go.mod @@ -1,24 +1,21 @@ module sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup -go 1.23.0 - -godebug default=go1.23 +go 1.24.0 require ( - github.com/cert-manager/cert-manager v1.17.1 + github.com/cert-manager/cert-manager v1.18.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 - k8s.io/api v0.32.1 - k8s.io/apimachinery v0.32.1 - k8s.io/client-go v0.32.1 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.20.4 + sigs.k8s.io/controller-runtime v0.21.0 ) require ( cel.dev/expr v0.19.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -37,27 +34,24 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.1 // indirect + github.com/google/cel-go v0.23.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.9.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.20.5 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -75,30 +69,31 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.28.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect google.golang.org/grpc v1.69.2 // indirect - google.golang.org/protobuf v1.36.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect - k8s.io/apiserver v0.32.1 // indirect - k8s.io/component-base v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.33.0 // indirect + k8s.io/apiserver v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/testdata/project-v4-multigroup/internal/controller/apps/deployment_controller.go b/testdata/project-v4-multigroup/internal/controller/apps/deployment_controller.go index 1d2e4ad967d..488b08ca2e3 100644 --- a/testdata/project-v4-multigroup/internal/controller/apps/deployment_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/apps/deployment_controller.go @@ -44,7 +44,7 @@ type DeploymentReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller.go b/testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller.go index a4d4de85946..804b543cedd 100644 --- a/testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller.go @@ -44,7 +44,7 @@ type CertificateReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/crew/captain_controller.go b/testdata/project-v4-multigroup/internal/controller/crew/captain_controller.go index d625a3333fb..46fda491bed 100644 --- a/testdata/project-v4-multigroup/internal/controller/crew/captain_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/crew/captain_controller.go @@ -45,7 +45,7 @@ type CaptainReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller.go b/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller.go index 3ab6ee18d45..3cca24c22aa 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller.go @@ -78,7 +78,7 @@ type BusyboxReconciler struct { // 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.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := logf.FromContext(ctx) @@ -99,7 +99,6 @@ 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 is available if 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 { @@ -123,12 +122,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { log.Info("Adding Finalizer for Busybox") - if ok := controllerutil.AddFinalizer(busybox, busyboxFinalizer); !ok { - err = fmt.Errorf("finalizer for Busybox was not added") - log.Error(err, "Failed to add finalizer for Busybox") - return ctrl.Result{}, err - } - + controllerutil.AddFinalizer(busybox, busyboxFinalizer) if err = r.Update(ctx, busybox); err != nil { log.Error(err, "Failed to update custom resource to add finalizer") return ctrl.Result{}, err @@ -233,13 +227,18 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } + // If the size is not defined in the Custom Resource then we will set the desired replicas to 0 + var desiredReplicas int32 = 0 + if busybox.Spec.Size != nil { + desiredReplicas = *busybox.Spec.Size + } + // 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 - if *found.Spec.Replicas != size { - found.Spec.Replicas = &size + if found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas { + found.Spec.Replicas = ptr.To(desiredReplicas) if err = r.Update(ctx, found); err != nil { log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) @@ -275,7 +274,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // The following implementation will update the status meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, size)}) + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, desiredReplicas)}) if err := r.Status().Update(ctx, busybox); err != nil { log.Error(err, "Failed to update Busybox status") @@ -309,7 +308,6 @@ func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alph func (r *BusyboxReconciler) deploymentForBusybox( busybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) { ls := labelsForBusybox() - replicas := busybox.Spec.Size // Get the Operand image image, err := imageForBusybox() @@ -323,7 +321,7 @@ func (r *BusyboxReconciler) deploymentForBusybox( Namespace: busybox.Namespace, }, Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, + Replicas: busybox.Spec.Size, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -419,7 +417,7 @@ func imageForBusybox() (string, error) { var imageEnvVar = "BUSYBOX_IMAGE" image, found := os.LookupEnv(imageEnvVar) if !found { - return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + return "", fmt.Errorf("unable to find %s environment variable with the image", imageEnvVar) } return image, nil } diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go b/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go index 5e88f26e714..e4c1deb12d8 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" @@ -71,13 +72,13 @@ var _ = Describe("Busybox controller", func() { 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 - busybox := &examplecomv1alpha1.Busybox{ + busybox = &examplecomv1alpha1.Busybox{ ObjectMeta: metav1.ObjectMeta{ Name: BusyboxName, Namespace: namespace.Name, }, Spec: examplecomv1alpha1.BusyboxSpec{ - Size: 1, + Size: ptr.To(int32(1)), }, } @@ -138,7 +139,7 @@ var _ = Describe("Busybox controller", func() { By("Checking the latest Status Condition added to the Busybox instance") Expect(k8sClient.Get(ctx, typeNamespacedName, busybox)).To(Succeed()) - conditions := []metav1.Condition{} + var conditions []metav1.Condition Expect(busybox.Status.Conditions).To(ContainElement( HaveField("Type", Equal(typeAvailableBusybox)), &conditions)) Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableBusybox) diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller.go b/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller.go index 9b14871eba1..cea2233e85a 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller.go @@ -78,7 +78,7 @@ type MemcachedReconciler struct { // 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.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := logf.FromContext(ctx) @@ -99,7 +99,6 @@ 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 is available if 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 { @@ -123,12 +122,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // 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 { - err = fmt.Errorf("finalizer for Memcached was not added") - log.Error(err, "Failed to add finalizer for Memcached") - return ctrl.Result{}, err - } - + controllerutil.AddFinalizer(memcached, memcachedFinalizer) if err = r.Update(ctx, memcached); err != nil { log.Error(err, "Failed to update custom resource to add finalizer") return ctrl.Result{}, err @@ -233,13 +227,18 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } + // If the size is not defined in the Custom Resource then we will set the desired replicas to 0 + var desiredReplicas int32 = 0 + if memcached.Spec.Size != nil { + desiredReplicas = *memcached.Spec.Size + } + // 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 found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas { + found.Spec.Replicas = ptr.To(desiredReplicas) if err = r.Update(ctx, found); err != nil { log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) @@ -275,7 +274,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // 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)}) + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", memcached.Name, desiredReplicas)}) if err := r.Status().Update(ctx, memcached); err != nil { log.Error(err, "Failed to update Memcached status") @@ -309,7 +308,6 @@ func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1 func (r *MemcachedReconciler) deploymentForMemcached( memcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) { ls := labelsForMemcached() - replicas := memcached.Spec.Size // Get the Operand image image, err := imageForMemcached() @@ -323,7 +321,7 @@ func (r *MemcachedReconciler) deploymentForMemcached( Namespace: memcached.Namespace, }, Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, + Replicas: memcached.Spec.Size, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -425,7 +423,7 @@ 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 "", fmt.Errorf("unable to find %s environment variable with the image", imageEnvVar) } return image, nil } diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller_test.go b/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller_test.go index 89be342f92c..673a08cf226 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller_test.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1" @@ -71,13 +72,13 @@ var _ = Describe("Memcached controller", func() { 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 := &examplecomv1alpha1.Memcached{ + memcached = &examplecomv1alpha1.Memcached{ ObjectMeta: metav1.ObjectMeta{ Name: MemcachedName, Namespace: namespace.Name, }, Spec: examplecomv1alpha1.MemcachedSpec{ - Size: 1, + Size: ptr.To(int32(1)), ContainerPort: 11211, }, } @@ -139,7 +140,7 @@ var _ = Describe("Memcached controller", func() { By("Checking the latest Status Condition added to the Memcached instance") Expect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed()) - conditions := []metav1.Condition{} + var conditions []metav1.Condition Expect(memcached.Status.Conditions).To(ContainElement( HaveField("Type", Equal(typeAvailableMemcached)), &conditions)) Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached) diff --git a/testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller.go b/testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller.go index c5cabb1bdd7..36ae78a641a 100644 --- a/testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller.go @@ -45,7 +45,7 @@ type WordpressReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *WordpressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/fiz/bar_controller.go b/testdata/project-v4-multigroup/internal/controller/fiz/bar_controller.go index 0af7eab90a2..7b0e7eb2af2 100644 --- a/testdata/project-v4-multigroup/internal/controller/fiz/bar_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/fiz/bar_controller.go @@ -45,7 +45,7 @@ type BarReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *BarReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller.go b/testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller.go index 9f6856b36cc..20dc34c8fd7 100644 --- a/testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller.go @@ -45,7 +45,7 @@ type HealthCheckPolicyReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *HealthCheckPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/foo/bar_controller.go b/testdata/project-v4-multigroup/internal/controller/foo/bar_controller.go index e72a66b97ab..1e67a26a91b 100644 --- a/testdata/project-v4-multigroup/internal/controller/foo/bar_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/foo/bar_controller.go @@ -45,7 +45,7 @@ type BarReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *BarReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller.go b/testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller.go index 26b2fb434d7..b60e46d419b 100644 --- a/testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller.go @@ -45,7 +45,7 @@ type KrakenReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *KrakenReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller.go b/testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller.go index 83e2f46e7b5..552c3d11ced 100644 --- a/testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller.go @@ -45,7 +45,7 @@ type LeviathanReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *LeviathanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller.go b/testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller.go index 96289d5bc3d..06acd8dde90 100644 --- a/testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller.go @@ -45,7 +45,7 @@ type CruiserReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *CruiserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller.go b/testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller.go index 62ea0fb9376..ac534d1309e 100644 --- a/testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller.go @@ -45,7 +45,7 @@ type DestroyerReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *DestroyerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/controller/ship/frigate_controller.go b/testdata/project-v4-multigroup/internal/controller/ship/frigate_controller.go index c588dd21349..be3c867b4ef 100644 --- a/testdata/project-v4-multigroup/internal/controller/ship/frigate_controller.go +++ b/testdata/project-v4-multigroup/internal/controller/ship/frigate_controller.go @@ -45,7 +45,7 @@ type FrigateReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *FrigateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook.go b/testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook.go index d838f845377..2fbc30f48e0 100644 --- a/testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook.go @@ -56,7 +56,7 @@ type DeploymentCustomDefaulter struct { var _ webhook.CustomDefaulter = &DeploymentCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Deployment. -func (d *DeploymentCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *DeploymentCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { deployment, ok := obj.(*appsv1.Deployment) if !ok { @@ -86,7 +86,7 @@ type DeploymentCustomValidator struct { var _ webhook.CustomValidator = &DeploymentCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Deployment. -func (v *DeploymentCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *DeploymentCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { deployment, ok := obj.(*appsv1.Deployment) if !ok { return nil, fmt.Errorf("expected a Deployment object but got %T", obj) @@ -99,7 +99,7 @@ func (v *DeploymentCustomValidator) ValidateCreate(ctx context.Context, obj runt } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Deployment. -func (v *DeploymentCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *DeploymentCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { deployment, ok := newObj.(*appsv1.Deployment) if !ok { return nil, fmt.Errorf("expected a Deployment object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook.go b/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook.go index 0577f525cc2..1ce37cdf796 100644 --- a/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook.go @@ -54,7 +54,7 @@ type IssuerCustomDefaulter struct { var _ webhook.CustomDefaulter = &IssuerCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Issuer. -func (d *IssuerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *IssuerCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { issuer, ok := obj.(*certmanagerv1.Issuer) if !ok { diff --git a/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go index 30f274046e0..51b9182a859 100644 --- a/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go @@ -58,7 +58,7 @@ type PodCustomValidator struct { var _ webhook.CustomValidator = &PodCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Pod. -func (v *PodCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *PodCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { pod, ok := obj.(*corev1.Pod) if !ok { return nil, fmt.Errorf("expected a Pod object but got %T", obj) @@ -71,7 +71,7 @@ func (v *PodCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Obj } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Pod. -func (v *PodCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *PodCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { pod, ok := newObj.(*corev1.Pod) if !ok { return nil, fmt.Errorf("expected a Pod object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook.go b/testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook.go index 8831faacf31..9becc8e8e05 100644 --- a/testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook.go @@ -57,7 +57,7 @@ type CaptainCustomDefaulter struct { var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain. -func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *CaptainCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { captain, ok := obj.(*crewv1.Captain) if !ok { @@ -87,7 +87,7 @@ type CaptainCustomValidator struct { var _ webhook.CustomValidator = &CaptainCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain. -func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *CaptainCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) @@ -100,7 +100,7 @@ func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain. -func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *CaptainCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { captain, ok := newObj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook.go b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook.go index c7c005c80e3..19507ce9b01 100644 --- a/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook.go @@ -59,7 +59,7 @@ type MemcachedCustomValidator struct { var _ webhook.CustomValidator = &MemcachedCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *MemcachedCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { memcached, ok := obj.(*examplecomv1alpha1.Memcached) if !ok { return nil, fmt.Errorf("expected a Memcached object but got %T", obj) @@ -72,7 +72,7 @@ func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runti } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *MemcachedCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { memcached, ok := newObj.(*examplecomv1alpha1.Memcached) if !ok { return nil, fmt.Errorf("expected a Memcached object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook.go b/testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook.go index ca6497e3172..eccd97db9b9 100644 --- a/testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook.go @@ -55,7 +55,7 @@ type DestroyerCustomDefaulter struct { var _ webhook.CustomDefaulter = &DestroyerCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Destroyer. -func (d *DestroyerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *DestroyerCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { destroyer, ok := obj.(*shipv1.Destroyer) if !ok { diff --git a/testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook.go b/testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook.go index 133d7c9ad9d..b88b9d9287f 100644 --- a/testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook.go +++ b/testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook.go @@ -59,7 +59,7 @@ type CruiserCustomValidator struct { var _ webhook.CustomValidator = &CruiserCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser. -func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *CruiserCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { cruiser, ok := obj.(*shipv2alpha1.Cruiser) if !ok { return nil, fmt.Errorf("expected a Cruiser object but got %T", obj) @@ -72,7 +72,7 @@ func (v *CruiserCustomValidator) ValidateCreate(ctx context.Context, obj runtime } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser. -func (v *CruiserCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *CruiserCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { cruiser, ok := newObj.(*shipv2alpha1.Cruiser) if !ok { return nil, fmt.Errorf("expected a Cruiser object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-multigroup/test/e2e/e2e_suite_test.go b/testdata/project-v4-multigroup/test/e2e/e2e_suite_test.go index 7fd194a822b..84de3b85ae3 100644 --- a/testdata/project-v4-multigroup/test/e2e/e2e_suite_test.go +++ b/testdata/project-v4-multigroup/test/e2e/e2e_suite_test.go @@ -43,7 +43,7 @@ var ( ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. +// temporary environment to validate project changes with the purpose of being used in CI jobs. // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs // CertManager. func TestE2E(t *testing.T) { diff --git a/testdata/project-v4-multigroup/test/e2e/e2e_test.go b/testdata/project-v4-multigroup/test/e2e/e2e_test.go index c131062cba8..c298a6bf4d0 100644 --- a/testdata/project-v4-multigroup/test/e2e/e2e_test.go +++ b/testdata/project-v4-multigroup/test/e2e/e2e_test.go @@ -221,6 +221,7 @@ var _ = Describe("Manager", Ordered, func() { "command": ["/bin/sh", "-c"], "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], "securityContext": { + "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] @@ -232,7 +233,7 @@ var _ = Describe("Manager", Ordered, func() { } } }], - "serviceAccount": "%s" + "serviceAccountName": "%s" } }`, token, metricsServiceName, namespace, serviceAccountName)) _, err = utils.Run(cmd) diff --git a/testdata/project-v4-multigroup/test/utils/utils.go b/testdata/project-v4-multigroup/test/utils/utils.go index 9419db9a8d0..1d6164b84bc 100644 --- a/testdata/project-v4-multigroup/test/utils/utils.go +++ b/testdata/project-v4-multigroup/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck ) const ( @@ -46,15 +46,15 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) output, err := cmd.CombinedOutput() if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return string(output), nil @@ -195,9 +195,9 @@ func GetNonEmptyLines(output string) []string { func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { - return wd, err + return wd, fmt.Errorf("failed to get current working directory: %w", err) } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } @@ -208,19 +208,19 @@ func UncommentCode(filename, target, prefix string) error { // nolint:gosec content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to output: %w", err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -228,24 +228,27 @@ func UncommentCode(filename, target, prefix string) error { return nil } for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } - if _, err := out.WriteString("\n"); err != nil { - return err + if _, err = out.WriteString("\n"); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } + // false positive // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } diff --git a/testdata/project-v4-with-plugins/.devcontainer/devcontainer.json b/testdata/project-v4-with-plugins/.devcontainer/devcontainer.json index 0e0eed213f7..a3ab7541cb6 100644 --- a/testdata/project-v4-with-plugins/.devcontainer/devcontainer.json +++ b/testdata/project-v4-with-plugins/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Kubebuilder DevContainer", - "image": "docker.io/golang:1.23", + "image": "golang:1.24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} diff --git a/testdata/project-v4-with-plugins/.github/workflows/lint.yml b/testdata/project-v4-with-plugins/.github/workflows/lint.yml index 4951e3316c1..67ff2bf09c0 100644 --- a/testdata/project-v4-with-plugins/.github/workflows/lint.yml +++ b/testdata/project-v4-with-plugins/.github/workflows/lint.yml @@ -18,6 +18,6 @@ jobs: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.63.4 + version: v2.1.6 diff --git a/testdata/project-v4-with-plugins/.github/workflows/test-e2e.yml b/testdata/project-v4-with-plugins/.github/workflows/test-e2e.yml index b2eda8c3db0..68fd1ed5562 100644 --- a/testdata/project-v4-with-plugins/.github/workflows/test-e2e.yml +++ b/testdata/project-v4-with-plugins/.github/workflows/test-e2e.yml @@ -26,9 +26,6 @@ jobs: - name: Verify kind installation run: kind version - - name: Create kind cluster - run: kind create cluster - - name: Running Test e2e run: | go mod tidy diff --git a/testdata/project-v4-with-plugins/.golangci.yml b/testdata/project-v4-with-plugins/.golangci.yml index 6b297462382..e5b21b0f11c 100644 --- a/testdata/project-v4-with-plugins/.golangci.yml +++ b/testdata/project-v4-with-plugins/.golangci.yml @@ -1,33 +1,15 @@ +version: "2" run: - timeout: 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 + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -36,12 +18,35 @@ linters: - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/testdata/project-v4-with-plugins/Dockerfile b/testdata/project-v4-with-plugins/Dockerfile index 348b8372cd1..cb1b130fd9d 100644 --- a/testdata/project-v4-with-plugins/Dockerfile +++ b/testdata/project-v4-with-plugins/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/golang:1.23 AS builder +FROM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/testdata/project-v4-with-plugins/Makefile b/testdata/project-v4-with-plugins/Makefile index 49f659fa94a..3b8bcb35d1f 100644 --- a/testdata/project-v4-with-plugins/Makefile +++ b/testdata/project-v4-with-plugins/Makefile @@ -65,17 +65,30 @@ test: manifests generate fmt vet setup-envtest ## Run tests. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # CertManager is installed by default; skip with: # - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +KIND_CLUSTER ?= project-v4-with-plugins-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist @command -v $(KIND) >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ } - @$(KIND) get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -173,12 +186,12 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.1.6 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -206,7 +219,7 @@ $(ENVTEST): $(LOCALBIN) .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)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/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 diff --git a/testdata/project-v4-with-plugins/PROJECT b/testdata/project-v4-with-plugins/PROJECT index 48f67097c0a..dc0cdc93ce6 100644 --- a/testdata/project-v4-with-plugins/PROJECT +++ b/testdata/project-v4-with-plugins/PROJECT @@ -2,6 +2,7 @@ # 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 +cliVersion: (devel) domain: testproject.org layout: - go.kubebuilder.io/v4 diff --git a/testdata/project-v4-with-plugins/README.md b/testdata/project-v4-with-plugins/README.md index 16de39f8ffa..3ad65a5c159 100644 --- a/testdata/project-v4-with-plugins/README.md +++ b/testdata/project-v4-with-plugins/README.md @@ -7,7 +7,7 @@ ## Getting Started ### Prerequisites -- go version v1.23.0+ +- go version v1.24.0+ - docker version 17.03+. - kubectl version v1.11.3+. - Access to a Kubernetes v1.11.3+ cluster. diff --git a/testdata/project-v4-with-plugins/api/v1/wordpress_types.go b/testdata/project-v4-with-plugins/api/v1/wordpress_types.go index 5e4f42981d2..be3dab2e98c 100644 --- a/testdata/project-v4-with-plugins/api/v1/wordpress_types.go +++ b/testdata/project-v4-with-plugins/api/v1/wordpress_types.go @@ -23,13 +23,16 @@ import ( // 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. -// WordpressSpec defines the desired state of Wordpress. +// WordpressSpec defines the desired state of Wordpress type WordpressSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Wordpress. Edit wordpress_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Wordpress. Edit wordpress_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // WordpressStatus defines the observed state of Wordpress. @@ -40,21 +43,28 @@ type WordpressStatus struct { // +kubebuilder:object:root=true // +kubebuilder:storageversion -// +kubebuilder:conversion:hub // +kubebuilder:subresource:status -// Wordpress is the Schema for the wordpresses API. +// Wordpress is the Schema for the wordpresses API type Wordpress struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Wordpress + // +required + Spec WordpressSpec `json:"spec"` - Spec WordpressSpec `json:"spec,omitempty"` - Status WordpressStatus `json:"status,omitempty"` + // status defines the observed state of Wordpress + // +optional + Status WordpressStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// WordpressList contains a list of Wordpress. +// WordpressList contains a list of Wordpress type WordpressList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go index 246c890cd59..879f751c77b 100644 --- a/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Wordpress) DeepCopyInto(out *Wordpress) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *WordpressList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec. diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/busybox_types.go b/testdata/project-v4-with-plugins/api/v1alpha1/busybox_types.go index 1ebc3c331e2..58f1f644d41 100644 --- a/testdata/project-v4-with-plugins/api/v1alpha1/busybox_types.go +++ b/testdata/project-v4-with-plugins/api/v1alpha1/busybox_types.go @@ -27,28 +27,34 @@ import ( type BusyboxSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - - // Size defines the number of Busybox 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"` + + // size defines the number of Busybox instances + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 + // +optional + Size *int32 `json:"size,omitempty"` } // BusyboxStatus defines the observed state of Busybox type BusyboxStatus struct { - // Represents the observations of a Busybox's current state. - // Busybox.status.conditions.type are: "Available", "Progressing", and "Degraded" - // Busybox.status.conditions.status are one of True, False, Unknown. - // Busybox.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. - // Busybox.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"` + // For Kubernetes API conventions, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + // conditions represent the current state of the Busybox resource. + // Each condition has a unique type and reflects the status of a specific aspect of the resource. + // + // Standard condition types include: + // - "Available": the resource is fully functional + // - "Progressing": the resource is being created or updated + // - "Degraded": the resource failed to reach or maintain its desired state + // + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true @@ -56,11 +62,19 @@ type BusyboxStatus struct { // Busybox is the Schema for the busyboxes API type Busybox struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Busybox + // +required + Spec BusyboxSpec `json:"spec"` - Spec BusyboxSpec `json:"spec,omitempty"` - Status BusyboxStatus `json:"status,omitempty"` + // status defines the observed state of Busybox + // +optional + Status BusyboxStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_types.go b/testdata/project-v4-with-plugins/api/v1alpha1/memcached_types.go index 7b9167c2dd5..e9a2b649dbb 100644 --- a/testdata/project-v4-with-plugins/api/v1alpha1/memcached_types.go +++ b/testdata/project-v4-with-plugins/api/v1alpha1/memcached_types.go @@ -27,31 +27,38 @@ import ( 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"` + // size defines the number of Memcached instances + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=0 + // +optional + Size *int32 `json:"size,omitempty"` + + // containerPort defines the port that will be used to init the container with the image + // +required + ContainerPort int32 `json:"containerPort"` } // 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"` + // For Kubernetes API conventions, see: + // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties + + // conditions represent the current state of the Memcached resource. + // Each condition has a unique type and reflects the status of a specific aspect of the resource. + // + // Standard condition types include: + // - "Available": the resource is fully functional + // - "Progressing": the resource is being created or updated + // - "Degraded": the resource failed to reach or maintain its desired state + // + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` } // +kubebuilder:object:root=true @@ -59,11 +66,19 @@ type MemcachedStatus struct { // Memcached is the Schema for the memcacheds API type Memcached struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Memcached + // +required + Spec MemcachedSpec `json:"spec"` - Spec MemcachedSpec `json:"spec,omitempty"` - Status MemcachedStatus `json:"status,omitempty"` + // status defines the observed state of Memcached + // +optional + Status MemcachedStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true diff --git a/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go index 340cb1ad656..c927b380d0c 100644 --- a/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go @@ -30,7 +30,7 @@ func (in *Busybox) DeepCopyInto(out *Busybox) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -87,6 +87,11 @@ func (in *BusyboxList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) { *out = *in + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec. @@ -126,7 +131,7 @@ func (in *Memcached) DeepCopyInto(out *Memcached) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -183,6 +188,11 @@ func (in *MemcachedList) DeepCopyObject() runtime.Object { // 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 + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. diff --git a/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go b/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go index dc0f1efaaba..35768d72894 100644 --- a/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go +++ b/testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go @@ -31,6 +31,12 @@ func (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v2 to v1 + // Example: Copying Spec fields + // dst.Spec.Size = src.Spec.Replicas + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } @@ -41,5 +47,11 @@ func (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v1 to v2 + // Example: Copying Spec fields + // dst.Spec.Replicas = src.Spec.Size + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } diff --git a/testdata/project-v4-with-plugins/api/v2/wordpress_types.go b/testdata/project-v4-with-plugins/api/v2/wordpress_types.go index 332bca6de7d..1eb81a467ec 100644 --- a/testdata/project-v4-with-plugins/api/v2/wordpress_types.go +++ b/testdata/project-v4-with-plugins/api/v2/wordpress_types.go @@ -23,13 +23,16 @@ import ( // 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. -// WordpressSpec defines the desired state of Wordpress. +// WordpressSpec defines the desired state of Wordpress type WordpressSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Wordpress. Edit wordpress_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Wordpress. Edit wordpress_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // WordpressStatus defines the observed state of Wordpress. @@ -41,18 +44,26 @@ type WordpressStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Wordpress is the Schema for the wordpresses API. +// Wordpress is the Schema for the wordpresses API type Wordpress struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Wordpress + // +required + Spec WordpressSpec `json:"spec"` - Spec WordpressSpec `json:"spec,omitempty"` - Status WordpressStatus `json:"status,omitempty"` + // status defines the observed state of Wordpress + // +optional + Status WordpressStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// WordpressList contains a list of Wordpress. +// WordpressList contains a list of Wordpress type WordpressList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go b/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go index a21d7c776b7..c5c3a08a3ae 100644 --- a/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Wordpress) DeepCopyInto(out *Wordpress) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *WordpressList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec. diff --git a/testdata/project-v4-with-plugins/cmd/main.go b/testdata/project-v4-with-plugins/cmd/main.go index 9f84f245175..f5c6727c399 100644 --- a/testdata/project-v4-with-plugins/cmd/main.go +++ b/testdata/project-v4-with-plugins/cmd/main.go @@ -41,8 +41,8 @@ import ( examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" examplecomv2 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v2" "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/controller" - webhookexamplecomv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1" - webhookexamplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1alpha1" + webhookv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1" + webhookv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -141,7 +141,7 @@ func main() { // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server // - https://book.kubebuilder.io/reference/metrics.html metricsServerOptions := metricsserver.Options{ BindAddress: metricsAddr, @@ -153,7 +153,7 @@ func main() { // FilterProvider is used to protect the metrics endpoint with authn/authz. // These configurations ensure that only authorized users and service accounts // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } @@ -208,7 +208,7 @@ func main() { os.Exit(1) } - if err = (&controller.MemcachedReconciler{ + if err := (&controller.MemcachedReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("memcached-controller"), @@ -216,7 +216,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Memcached") os.Exit(1) } - if err = (&controller.BusyboxReconciler{ + if err := (&controller.BusyboxReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("busybox-controller"), @@ -226,12 +226,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { + if err := webhookv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Memcached") os.Exit(1) } } - if err = (&controller.WordpressReconciler{ + if err := (&controller.WordpressReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -240,7 +240,7 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookexamplecomv1.SetupWordpressWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupWordpressWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Wordpress") os.Exit(1) } diff --git a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_busyboxes.yaml b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_busyboxes.yaml index f22e8ee9ba9..11fb23ee219 100644 --- a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_busyboxes.yaml +++ b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_busyboxes.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: busyboxes.example.com.testproject.org spec: group: example.com.testproject.org @@ -37,22 +37,29 @@ spec: metadata: type: object spec: - description: BusyboxSpec defines the desired state of Busybox + description: spec defines the desired state of Busybox properties: size: - description: |- - Size defines the number of Busybox instances - The following markers will use OpenAPI v3 schema to validate the value - More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + default: 1 + description: size defines the number of Busybox instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer type: object status: - description: BusyboxStatus defines the observed state of Busybox + description: status defines the observed state of Busybox properties: conditions: + description: |- + conditions represent the current state of the Busybox resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -108,7 +115,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_memcacheds.yaml b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_memcacheds.yaml index 2abcfada046..d3bbbe38c34 100644 --- a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_memcacheds.yaml +++ b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_memcacheds.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: memcacheds.example.com.testproject.org spec: group: example.com.testproject.org @@ -37,27 +37,36 @@ spec: metadata: type: object spec: - description: MemcachedSpec defines the desired state of Memcached + description: spec defines the desired state of Memcached properties: containerPort: - description: Port defines the port that will be used to init the container - with the image + description: containerPort 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 + default: 1 + description: size defines the number of Memcached instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer + required: + - containerPort type: object status: - description: MemcachedStatus defines the observed state of Memcached + description: status defines the observed state of Memcached properties: conditions: + description: |- + conditions represent the current state of the Memcached resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -113,7 +122,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_wordpresses.yaml b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_wordpresses.yaml index 12280a20543..5a9493bc6c3 100644 --- a/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_wordpresses.yaml +++ b/testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_wordpresses.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: wordpresses.example.com.testproject.org spec: group: example.com.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: true @@ -55,7 +57,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -75,16 +77,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: false diff --git a/testdata/project-v4-with-plugins/config/default/kustomization.yaml b/testdata/project-v4-with-plugins/config/default/kustomization.yaml index a9d169cbd76..a455b3774b1 100644 --- a/testdata/project-v4-with-plugins/config/default/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/default/kustomization.yaml @@ -22,7 +22,7 @@ resources: # crd/kustomization.yaml - ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus # [METRICS] Expose the controller manager metrics service. @@ -56,7 +56,7 @@ patches: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: +replacements: # - source: # Uncomment the following block to enable certificates for metrics # kind: Service # version: v1 @@ -86,7 +86,7 @@ patches: # delimiter: '.' # index: 0 # create: true -# + # - source: # kind: Service # version: v1 @@ -116,75 +116,75 @@ patches: # delimiter: '.' # index: 1 # create: true -# -# - source: # Uncomment the following block if you have any webhook -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # Name of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# 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 -# name: serving-cert -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) -# 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 -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# + + - source: # Uncomment the following block if you have any webhook + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name # Name of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + 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 + name: serving-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true + + - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) + 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 + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + # - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) # kind: Certificate # group: cert-manager.io @@ -215,38 +215,38 @@ patches: # delimiter: '/' # index: 1 # create: true -# -# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: wordpresses.example.com.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true + + - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.namespace # Namespace of the certificate CR + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: wordpresses.example.com.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionns -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: wordpresses.example.com.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: wordpresses.example.com.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionname diff --git a/testdata/project-v4-with-plugins/config/manager/manager.yaml b/testdata/project-v4-with-plugins/config/manager/manager.yaml index b4266ddbbcf..82c7dbc38c3 100644 --- a/testdata/project-v4-with-plugins/config/manager/manager.yaml +++ b/testdata/project-v4-with-plugins/config/manager/manager.yaml @@ -72,6 +72,7 @@ spec: value: memcached:1.6.26-alpine3.19 ports: [] securityContext: + readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: diff --git a/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml b/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml index c4309d1a86b..6791a6d2222 100644 --- a/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml +++ b/testdata/project-v4-with-plugins/config/rbac/kustomization.yaml @@ -20,7 +20,7 @@ resources: - metrics_reader_role.yaml # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are -# not used by the {{ .ProjectName }} itself. You can comment the following lines +# not used by the project-v4-with-plugins itself. You can comment the following lines # if you do not want those helpers be installed with your Project. - wordpress_admin_role.yaml - wordpress_editor_role.yaml diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml old mode 100755 new mode 100644 index f1610fbcad4..9b4fa198269 --- a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_busyboxes.yaml @@ -9,7 +9,7 @@ metadata: {{- if .Values.crd.keep }} "helm.sh/resource-policy": keep {{- end }} - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: busyboxes.example.com.testproject.org spec: group: example.com.testproject.org @@ -43,22 +43,29 @@ spec: metadata: type: object spec: - description: BusyboxSpec defines the desired state of Busybox + description: spec defines the desired state of Busybox properties: size: - description: |- - Size defines the number of Busybox instances - The following markers will use OpenAPI v3 schema to validate the value - More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + default: 1 + description: size defines the number of Busybox instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer type: object status: - description: BusyboxStatus defines the observed state of Busybox + description: status defines the observed state of Busybox properties: conditions: + description: |- + conditions represent the current state of the Busybox resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -114,7 +121,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml old mode 100755 new mode 100644 index 6e097f6eee7..22d8555bfc5 --- a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_memcacheds.yaml @@ -9,7 +9,7 @@ metadata: {{- if .Values.crd.keep }} "helm.sh/resource-policy": keep {{- end }} - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: memcacheds.example.com.testproject.org spec: group: example.com.testproject.org @@ -43,27 +43,36 @@ spec: metadata: type: object spec: - description: MemcachedSpec defines the desired state of Memcached + description: spec defines the desired state of Memcached properties: containerPort: - description: Port defines the port that will be used to init the container - with the image + description: containerPort 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 + default: 1 + description: size defines the number of Memcached instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer + required: + - containerPort type: object status: - description: MemcachedStatus defines the observed state of Memcached + description: status defines the observed state of Memcached properties: conditions: + description: |- + conditions represent the current state of the Memcached resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -119,7 +128,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml old mode 100755 new mode 100644 index b972f36f2e7..788931835b9 --- a/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/templates/crd/example.com.testproject.org_wordpresses.yaml @@ -12,7 +12,7 @@ metadata: {{- if .Values.crd.keep }} "helm.sh/resource-policy": keep {{- end }} - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: wordpresses.example.com.testproject.org spec: {{- if .Values.webhook.enable }} @@ -22,7 +22,7 @@ spec: clientConfig: service: namespace: {{ .Release.Namespace }} - name: webhook-service + name: project-v4-with-plugins-webhook-service path: /convert conversionReviewVersions: - v1 @@ -38,7 +38,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -58,16 +58,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: true @@ -76,7 +78,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -96,16 +98,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: false diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml index b25499bf53a..d4ea4d3eed5 100644 --- a/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/templates/metrics/metrics-service.yaml @@ -6,6 +6,7 @@ metadata: namespace: {{ .Release.Namespace }} labels: {{- include "chart.labels" . | nindent 4 }} + control-plane: controller-manager spec: ports: - port: 8443 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-metrics-traffic.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/network-policy/allow-webhook-traffic.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml index 92773eb66b9..522536e7e1e 100644 --- a/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml +++ b/testdata/project-v4-with-plugins/dist/chart/templates/prometheus/monitor.yaml @@ -5,6 +5,7 @@ kind: ServiceMonitor metadata: labels: {{- include "chart.labels" . | nindent 4 }} + control-plane: controller-manager name: project-v4-with-plugins-controller-manager-metrics-monitor namespace: {{ .Release.Namespace }} spec: diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_admin_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_editor_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox_viewer_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader_election_role_binding.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_admin_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_editor_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached_viewer_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_auth_role_binding.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics_reader_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/role_binding.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/service_account.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_admin_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_admin_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_editor_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml b/testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress_viewer_role.yaml old mode 100755 new mode 100644 diff --git a/testdata/project-v4-with-plugins/dist/install.yaml b/testdata/project-v4-with-plugins/dist/install.yaml index 8d43ea7baf1..56b971e5df3 100644 --- a/testdata/project-v4-with-plugins/dist/install.yaml +++ b/testdata/project-v4-with-plugins/dist/install.yaml @@ -11,7 +11,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: busyboxes.example.com.testproject.org spec: group: example.com.testproject.org @@ -45,22 +45,29 @@ spec: metadata: type: object spec: - description: BusyboxSpec defines the desired state of Busybox + description: spec defines the desired state of Busybox properties: size: - description: |- - Size defines the number of Busybox instances - The following markers will use OpenAPI v3 schema to validate the value - More info: https://book.kubebuilder.io/reference/markers/crd-validation.html + default: 1 + description: size defines the number of Busybox instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer type: object status: - description: BusyboxStatus defines the observed state of Busybox + description: status defines the observed state of Busybox properties: conditions: + description: |- + conditions represent the current state of the Busybox resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -116,7 +123,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true @@ -127,7 +139,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: memcacheds.example.com.testproject.org spec: group: example.com.testproject.org @@ -161,27 +173,36 @@ spec: metadata: type: object spec: - description: MemcachedSpec defines the desired state of Memcached + description: spec defines the desired state of Memcached properties: containerPort: - description: Port defines the port that will be used to init the container - with the image + description: containerPort 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 + default: 1 + description: size defines the number of Memcached instances format: int32 - maximum: 3 - minimum: 1 + minimum: 0 type: integer + required: + - containerPort type: object status: - description: MemcachedStatus defines the observed state of Memcached + description: status defines the observed state of Memcached properties: conditions: + description: |- + conditions represent the current state of the Memcached resource. + Each condition has a unique type and reflects the status of a specific aspect of the resource. + + Standard condition types include: + - "Available": the resource is fully functional + - "Progressing": the resource is being created or updated + - "Degraded": the resource failed to reach or maintain its desired state + + The status of each condition is one of True, False, or Unknown. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -237,7 +258,12 @@ spec: - type type: object type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map type: object + required: + - spec type: object served: true storage: true @@ -248,7 +274,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + cert-manager.io/inject-ca-from: project-v4-with-plugins-system/project-v4-with-plugins-serving-cert + controller-gen.kubebuilder.io/version: v0.18.0 name: wordpresses.example.com.testproject.org spec: conversion: @@ -272,7 +299,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -292,16 +319,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: true @@ -310,7 +339,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: Wordpress is the Schema for the wordpresses API. + description: Wordpress is the Schema for the wordpresses API properties: apiVersion: description: |- @@ -330,16 +359,18 @@ spec: metadata: type: object spec: - description: WordpressSpec defines the desired state of Wordpress. + description: spec defines the desired state of Wordpress properties: foo: - description: Foo is an example field of Wordpress. Edit wordpress_types.go + description: foo is an example field of Wordpress. Edit wordpress_types.go to remove/update type: string type: object status: - description: WordpressStatus defines the observed state of Wordpress. + description: status defines the observed state of Wordpress type: object + required: + - spec type: object served: true storage: false @@ -850,6 +881,7 @@ spec: capabilities: drop: - ALL + readOnlyRootFilesystem: true volumeMounts: - mountPath: /tmp/k8s-webhook-server/serving-certs name: webhook-certs @@ -865,9 +897,56 @@ spec: secret: secretName: webhook-server-cert --- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-with-plugins + name: project-v4-with-plugins-metrics-certs + namespace: project-v4-with-plugins-system +spec: + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-with-plugins-selfsigned-issuer + secretName: metrics-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-with-plugins + name: project-v4-with-plugins-serving-cert + namespace: project-v4-with-plugins-system +spec: + dnsNames: + - project-v4-with-plugins-webhook-service.project-v4-with-plugins-system.svc + - project-v4-with-plugins-webhook-service.project-v4-with-plugins-system.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-with-plugins-selfsigned-issuer + secretName: webhook-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4-with-plugins + name: project-v4-with-plugins-selfsigned-issuer + namespace: project-v4-with-plugins-system +spec: + selfSigned: {} +--- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: + annotations: + cert-manager.io/inject-ca-from: project-v4-with-plugins-system/project-v4-with-plugins-serving-cert name: project-v4-with-plugins-validating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/testdata/project-v4-with-plugins/go.mod b/testdata/project-v4-with-plugins/go.mod index 8185a3cad63..b18dba63afc 100644 --- a/testdata/project-v4-with-plugins/go.mod +++ b/testdata/project-v4-with-plugins/go.mod @@ -1,28 +1,25 @@ module sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins -go 1.23.0 - -godebug default=go1.23 +go 1.24.0 require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 - k8s.io/api v0.32.1 - k8s.io/apimachinery v0.32.1 - k8s.io/client-go v0.32.1 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 - sigs.k8s.io/controller-runtime v0.20.4 + sigs.k8s.io/controller-runtime v0.21.0 ) require ( - cel.dev/expr v0.18.0 // indirect + cel.dev/expr v0.19.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -36,15 +33,13 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.0 // 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/cel-go v0.23.2 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -53,48 +48,50 @@ require ( 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.19.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect - k8s.io/apiserver v0.32.1 // indirect - k8s.io/component-base v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.33.0 // indirect + k8s.io/apiserver v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/testdata/project-v4-with-plugins/internal/controller/busybox_controller.go b/testdata/project-v4-with-plugins/internal/controller/busybox_controller.go index d0a1c7be756..bf9f349aaef 100644 --- a/testdata/project-v4-with-plugins/internal/controller/busybox_controller.go +++ b/testdata/project-v4-with-plugins/internal/controller/busybox_controller.go @@ -78,7 +78,7 @@ type BusyboxReconciler struct { // 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.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := logf.FromContext(ctx) @@ -99,7 +99,6 @@ 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 is available if 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 { @@ -123,12 +122,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { log.Info("Adding Finalizer for Busybox") - if ok := controllerutil.AddFinalizer(busybox, busyboxFinalizer); !ok { - err = fmt.Errorf("finalizer for Busybox was not added") - log.Error(err, "Failed to add finalizer for Busybox") - return ctrl.Result{}, err - } - + controllerutil.AddFinalizer(busybox, busyboxFinalizer) if err = r.Update(ctx, busybox); err != nil { log.Error(err, "Failed to update custom resource to add finalizer") return ctrl.Result{}, err @@ -233,13 +227,18 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } + // If the size is not defined in the Custom Resource then we will set the desired replicas to 0 + var desiredReplicas int32 = 0 + if busybox.Spec.Size != nil { + desiredReplicas = *busybox.Spec.Size + } + // 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 - if *found.Spec.Replicas != size { - found.Spec.Replicas = &size + if found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas { + found.Spec.Replicas = ptr.To(desiredReplicas) if err = r.Update(ctx, found); err != nil { log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) @@ -275,7 +274,7 @@ func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // The following implementation will update the status meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, size)}) + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, desiredReplicas)}) if err := r.Status().Update(ctx, busybox); err != nil { log.Error(err, "Failed to update Busybox status") @@ -309,7 +308,6 @@ func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alph func (r *BusyboxReconciler) deploymentForBusybox( busybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) { ls := labelsForBusybox() - replicas := busybox.Spec.Size // Get the Operand image image, err := imageForBusybox() @@ -323,7 +321,7 @@ func (r *BusyboxReconciler) deploymentForBusybox( Namespace: busybox.Namespace, }, Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, + Replicas: busybox.Spec.Size, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -419,7 +417,7 @@ func imageForBusybox() (string, error) { var imageEnvVar = "BUSYBOX_IMAGE" image, found := os.LookupEnv(imageEnvVar) if !found { - return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + return "", fmt.Errorf("unable to find %s environment variable with the image", imageEnvVar) } return image, nil } diff --git a/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go b/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go index ce3693f883e..7c3417e9b44 100644 --- a/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go +++ b/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" @@ -71,13 +72,13 @@ var _ = Describe("Busybox controller", func() { 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 - busybox := &examplecomv1alpha1.Busybox{ + busybox = &examplecomv1alpha1.Busybox{ ObjectMeta: metav1.ObjectMeta{ Name: BusyboxName, Namespace: namespace.Name, }, Spec: examplecomv1alpha1.BusyboxSpec{ - Size: 1, + Size: ptr.To(int32(1)), }, } @@ -138,7 +139,7 @@ var _ = Describe("Busybox controller", func() { By("Checking the latest Status Condition added to the Busybox instance") Expect(k8sClient.Get(ctx, typeNamespacedName, busybox)).To(Succeed()) - conditions := []metav1.Condition{} + var conditions []metav1.Condition Expect(busybox.Status.Conditions).To(ContainElement( HaveField("Type", Equal(typeAvailableBusybox)), &conditions)) Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableBusybox) diff --git a/testdata/project-v4-with-plugins/internal/controller/memcached_controller.go b/testdata/project-v4-with-plugins/internal/controller/memcached_controller.go index d0d4ec62e4f..471a9f130a6 100644 --- a/testdata/project-v4-with-plugins/internal/controller/memcached_controller.go +++ b/testdata/project-v4-with-plugins/internal/controller/memcached_controller.go @@ -78,7 +78,7 @@ type MemcachedReconciler struct { // 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.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := logf.FromContext(ctx) @@ -99,7 +99,6 @@ 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 is available if 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 { @@ -123,12 +122,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // 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 { - err = fmt.Errorf("finalizer for Memcached was not added") - log.Error(err, "Failed to add finalizer for Memcached") - return ctrl.Result{}, err - } - + controllerutil.AddFinalizer(memcached, memcachedFinalizer) if err = r.Update(ctx, memcached); err != nil { log.Error(err, "Failed to update custom resource to add finalizer") return ctrl.Result{}, err @@ -233,13 +227,18 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } + // If the size is not defined in the Custom Resource then we will set the desired replicas to 0 + var desiredReplicas int32 = 0 + if memcached.Spec.Size != nil { + desiredReplicas = *memcached.Spec.Size + } + // 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 found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas { + found.Spec.Replicas = ptr.To(desiredReplicas) if err = r.Update(ctx, found); err != nil { log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) @@ -275,7 +274,7 @@ func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // 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)}) + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", memcached.Name, desiredReplicas)}) if err := r.Status().Update(ctx, memcached); err != nil { log.Error(err, "Failed to update Memcached status") @@ -309,7 +308,6 @@ func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1 func (r *MemcachedReconciler) deploymentForMemcached( memcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) { ls := labelsForMemcached() - replicas := memcached.Spec.Size // Get the Operand image image, err := imageForMemcached() @@ -323,7 +321,7 @@ func (r *MemcachedReconciler) deploymentForMemcached( Namespace: memcached.Namespace, }, Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, + Replicas: memcached.Spec.Size, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -425,7 +423,7 @@ 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 "", fmt.Errorf("unable to find %s environment variable with the image", imageEnvVar) } return image, nil } diff --git a/testdata/project-v4-with-plugins/internal/controller/memcached_controller_test.go b/testdata/project-v4-with-plugins/internal/controller/memcached_controller_test.go index de31688587b..a47a7890727 100644 --- a/testdata/project-v4-with-plugins/internal/controller/memcached_controller_test.go +++ b/testdata/project-v4-with-plugins/internal/controller/memcached_controller_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1" @@ -71,13 +72,13 @@ var _ = Describe("Memcached controller", func() { 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 := &examplecomv1alpha1.Memcached{ + memcached = &examplecomv1alpha1.Memcached{ ObjectMeta: metav1.ObjectMeta{ Name: MemcachedName, Namespace: namespace.Name, }, Spec: examplecomv1alpha1.MemcachedSpec{ - Size: 1, + Size: ptr.To(int32(1)), ContainerPort: 11211, }, } @@ -139,7 +140,7 @@ var _ = Describe("Memcached controller", func() { By("Checking the latest Status Condition added to the Memcached instance") Expect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed()) - conditions := []metav1.Condition{} + var conditions []metav1.Condition Expect(memcached.Status.Conditions).To(ContainElement( HaveField("Type", Equal(typeAvailableMemcached)), &conditions)) Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached) diff --git a/testdata/project-v4-with-plugins/internal/controller/wordpress_controller.go b/testdata/project-v4-with-plugins/internal/controller/wordpress_controller.go index 318acdcedf7..5251d893a4d 100644 --- a/testdata/project-v4-with-plugins/internal/controller/wordpress_controller.go +++ b/testdata/project-v4-with-plugins/internal/controller/wordpress_controller.go @@ -45,7 +45,7 @@ type WordpressReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *WordpressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook.go b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook.go index b6f559f8759..f84e6ffc441 100644 --- a/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook.go +++ b/testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook.go @@ -59,7 +59,7 @@ type MemcachedCustomValidator struct { var _ webhook.CustomValidator = &MemcachedCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *MemcachedCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { memcached, ok := obj.(*examplecomv1alpha1.Memcached) if !ok { return nil, fmt.Errorf("expected a Memcached object but got %T", obj) @@ -72,7 +72,7 @@ func (v *MemcachedCustomValidator) ValidateCreate(ctx context.Context, obj runti } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached. -func (v *MemcachedCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *MemcachedCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { memcached, ok := newObj.(*examplecomv1alpha1.Memcached) if !ok { return nil, fmt.Errorf("expected a Memcached object for the newObj but got %T", newObj) diff --git a/testdata/project-v4-with-plugins/test/e2e/e2e_suite_test.go b/testdata/project-v4-with-plugins/test/e2e/e2e_suite_test.go index aa118e65f9b..e7cfa25ddde 100644 --- a/testdata/project-v4-with-plugins/test/e2e/e2e_suite_test.go +++ b/testdata/project-v4-with-plugins/test/e2e/e2e_suite_test.go @@ -43,7 +43,7 @@ var ( ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. +// temporary environment to validate project changes with the purpose of being used in CI jobs. // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs // CertManager. func TestE2E(t *testing.T) { diff --git a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go index 3deae60f3f0..dafabd68d9c 100644 --- a/testdata/project-v4-with-plugins/test/e2e/e2e_test.go +++ b/testdata/project-v4-with-plugins/test/e2e/e2e_test.go @@ -221,6 +221,7 @@ var _ = Describe("Manager", Ordered, func() { "command": ["/bin/sh", "-c"], "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], "securityContext": { + "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] @@ -232,7 +233,7 @@ var _ = Describe("Manager", Ordered, func() { } } }], - "serviceAccount": "%s" + "serviceAccountName": "%s" } }`, token, metricsServiceName, namespace, serviceAccountName)) _, err = utils.Run(cmd) diff --git a/testdata/project-v4-with-plugins/test/utils/utils.go b/testdata/project-v4-with-plugins/test/utils/utils.go index 9419db9a8d0..1d6164b84bc 100644 --- a/testdata/project-v4-with-plugins/test/utils/utils.go +++ b/testdata/project-v4-with-plugins/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck ) const ( @@ -46,15 +46,15 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) output, err := cmd.CombinedOutput() if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return string(output), nil @@ -195,9 +195,9 @@ func GetNonEmptyLines(output string) []string { func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { - return wd, err + return wd, fmt.Errorf("failed to get current working directory: %w", err) } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } @@ -208,19 +208,19 @@ func UncommentCode(filename, target, prefix string) error { // nolint:gosec content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to output: %w", err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -228,24 +228,27 @@ func UncommentCode(filename, target, prefix string) error { return nil } for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } - if _, err := out.WriteString("\n"); err != nil { - return err + if _, err = out.WriteString("\n"); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } + // false positive // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } diff --git a/testdata/project-v4/.devcontainer/devcontainer.json b/testdata/project-v4/.devcontainer/devcontainer.json index 0e0eed213f7..a3ab7541cb6 100644 --- a/testdata/project-v4/.devcontainer/devcontainer.json +++ b/testdata/project-v4/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Kubebuilder DevContainer", - "image": "docker.io/golang:1.23", + "image": "golang:1.24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} diff --git a/testdata/project-v4/.github/workflows/lint.yml b/testdata/project-v4/.github/workflows/lint.yml index 4951e3316c1..67ff2bf09c0 100644 --- a/testdata/project-v4/.github/workflows/lint.yml +++ b/testdata/project-v4/.github/workflows/lint.yml @@ -18,6 +18,6 @@ jobs: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.63.4 + version: v2.1.6 diff --git a/testdata/project-v4/.github/workflows/test-e2e.yml b/testdata/project-v4/.github/workflows/test-e2e.yml index b2eda8c3db0..68fd1ed5562 100644 --- a/testdata/project-v4/.github/workflows/test-e2e.yml +++ b/testdata/project-v4/.github/workflows/test-e2e.yml @@ -26,9 +26,6 @@ jobs: - name: Verify kind installation run: kind version - - name: Create kind cluster - run: kind create cluster - - name: Running Test e2e run: | go mod tidy diff --git a/testdata/project-v4/.golangci.yml b/testdata/project-v4/.golangci.yml index 6b297462382..e5b21b0f11c 100644 --- a/testdata/project-v4/.golangci.yml +++ b/testdata/project-v4/.golangci.yml @@ -1,33 +1,15 @@ +version: "2" run: - timeout: 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 + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -36,12 +18,35 @@ linters: - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/testdata/project-v4/Dockerfile b/testdata/project-v4/Dockerfile index 348b8372cd1..cb1b130fd9d 100644 --- a/testdata/project-v4/Dockerfile +++ b/testdata/project-v4/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/golang:1.23 AS builder +FROM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/testdata/project-v4/Makefile b/testdata/project-v4/Makefile index b8f4b95e623..165de984427 100644 --- a/testdata/project-v4/Makefile +++ b/testdata/project-v4/Makefile @@ -65,17 +65,30 @@ test: manifests generate fmt vet setup-envtest ## Run tests. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # CertManager is installed by default; skip with: # - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +KIND_CLUSTER ?= project-v4-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist @command -v $(KIND) >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ } - @$(KIND) get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -173,12 +186,12 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.1.6 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -206,7 +219,7 @@ $(ENVTEST): $(LOCALBIN) .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)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/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 diff --git a/testdata/project-v4/PROJECT b/testdata/project-v4/PROJECT index dee611b980b..608fd538064 100644 --- a/testdata/project-v4/PROJECT +++ b/testdata/project-v4/PROJECT @@ -2,6 +2,7 @@ # 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 +cliVersion: (devel) domain: testproject.org layout: - go.kubebuilder.io/v4 diff --git a/testdata/project-v4/README.md b/testdata/project-v4/README.md index f3dcd6606f2..b6e8414c82a 100644 --- a/testdata/project-v4/README.md +++ b/testdata/project-v4/README.md @@ -7,7 +7,7 @@ ## Getting Started ### Prerequisites -- go version v1.23.0+ +- go version v1.24.0+ - docker version 17.03+. - kubectl version v1.11.3+. - Access to a Kubernetes v1.11.3+ cluster. diff --git a/testdata/project-v4/api/v1/admiral_types.go b/testdata/project-v4/api/v1/admiral_types.go index 8cb9856f199..6a87fb9402c 100644 --- a/testdata/project-v4/api/v1/admiral_types.go +++ b/testdata/project-v4/api/v1/admiral_types.go @@ -23,13 +23,16 @@ import ( // 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. -// AdmiralSpec defines the desired state of Admiral. +// AdmiralSpec defines the desired state of Admiral type AdmiralSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Admiral. Edit admiral_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Admiral. Edit admiral_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // AdmiralStatus defines the observed state of Admiral. @@ -42,18 +45,26 @@ type AdmiralStatus struct { // +kubebuilder:subresource:status // +kubebuilder:resource:path=admirales,scope=Cluster -// Admiral is the Schema for the admirales API. +// Admiral is the Schema for the admirales API type Admiral struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Admiral + // +required + Spec AdmiralSpec `json:"spec"` - Spec AdmiralSpec `json:"spec,omitempty"` - Status AdmiralStatus `json:"status,omitempty"` + // status defines the observed state of Admiral + // +optional + Status AdmiralStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// AdmiralList contains a list of Admiral. +// AdmiralList contains a list of Admiral type AdmiralList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4/api/v1/captain_types.go b/testdata/project-v4/api/v1/captain_types.go index 84f051e64c0..7c7b0939774 100644 --- a/testdata/project-v4/api/v1/captain_types.go +++ b/testdata/project-v4/api/v1/captain_types.go @@ -23,13 +23,16 @@ import ( // 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. -// CaptainSpec defines the desired state of Captain. +// CaptainSpec defines the desired state of Captain type CaptainSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of Captain. Edit captain_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of Captain. Edit captain_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // CaptainStatus defines the observed state of Captain. @@ -41,18 +44,26 @@ type CaptainStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// Captain is the Schema for the captains API. +// Captain is the Schema for the captains API type Captain struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of Captain + // +required + Spec CaptainSpec `json:"spec"` - Spec CaptainSpec `json:"spec,omitempty"` - Status CaptainStatus `json:"status,omitempty"` + // status defines the observed state of Captain + // +optional + Status CaptainStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// CaptainList contains a list of Captain. +// CaptainList contains a list of Captain type CaptainList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4/api/v1/firstmate_types.go b/testdata/project-v4/api/v1/firstmate_types.go index 6d3d6f1d1e4..f07b5643d30 100644 --- a/testdata/project-v4/api/v1/firstmate_types.go +++ b/testdata/project-v4/api/v1/firstmate_types.go @@ -23,13 +23,16 @@ import ( // 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. -// FirstMateSpec defines the desired state of FirstMate. +// FirstMateSpec defines the desired state of FirstMate type FirstMateSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of FirstMate. Edit firstmate_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of FirstMate. Edit firstmate_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // FirstMateStatus defines the observed state of FirstMate. @@ -40,21 +43,28 @@ type FirstMateStatus struct { // +kubebuilder:object:root=true // +kubebuilder:storageversion -// +kubebuilder:conversion:hub // +kubebuilder:subresource:status -// FirstMate is the Schema for the firstmates API. +// FirstMate is the Schema for the firstmates API type FirstMate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of FirstMate + // +required + Spec FirstMateSpec `json:"spec"` - Spec FirstMateSpec `json:"spec,omitempty"` - Status FirstMateStatus `json:"status,omitempty"` + // status defines the observed state of FirstMate + // +optional + Status FirstMateStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// FirstMateList contains a list of FirstMate. +// FirstMateList contains a list of FirstMate type FirstMateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4/api/v1/zz_generated.deepcopy.go b/testdata/project-v4/api/v1/zz_generated.deepcopy.go index 11a2029e6d7..eb1d2296a95 100644 --- a/testdata/project-v4/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4/api/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *Admiral) DeepCopyInto(out *Admiral) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *AdmiralList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AdmiralSpec) DeepCopyInto(out *AdmiralSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralSpec. @@ -118,7 +123,7 @@ func (in *Captain) DeepCopyInto(out *Captain) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -175,6 +180,11 @@ func (in *CaptainList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec. @@ -207,7 +217,7 @@ func (in *FirstMate) DeepCopyInto(out *FirstMate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -264,6 +274,11 @@ func (in *FirstMateList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec. diff --git a/testdata/project-v4/api/v2/firstmate_conversion.go b/testdata/project-v4/api/v2/firstmate_conversion.go index 7ad5cf36045..b7181ac5411 100644 --- a/testdata/project-v4/api/v2/firstmate_conversion.go +++ b/testdata/project-v4/api/v2/firstmate_conversion.go @@ -31,6 +31,12 @@ func (src *FirstMate) ConvertTo(dstRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v2 to v1 + // Example: Copying Spec fields + // dst.Spec.Size = src.Spec.Replicas + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } @@ -41,5 +47,11 @@ func (dst *FirstMate) ConvertFrom(srcRaw conversion.Hub) error { "source: %s/%s, target: %s/%s", src.Namespace, src.Name, dst.Namespace, dst.Name) // TODO(user): Implement conversion logic from v1 to v2 + // Example: Copying Spec fields + // dst.Spec.Replicas = src.Spec.Size + + // Copy ObjectMeta to preserve name, namespace, labels, etc. + dst.ObjectMeta = src.ObjectMeta + return nil } diff --git a/testdata/project-v4/api/v2/firstmate_types.go b/testdata/project-v4/api/v2/firstmate_types.go index f9255823031..41624134eab 100644 --- a/testdata/project-v4/api/v2/firstmate_types.go +++ b/testdata/project-v4/api/v2/firstmate_types.go @@ -23,13 +23,16 @@ import ( // 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. -// FirstMateSpec defines the desired state of FirstMate. +// FirstMateSpec defines the desired state of FirstMate type FirstMateSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + // The following markers will use OpenAPI v3 schema to validate the value + // More info: https://book.kubebuilder.io/reference/markers/crd-validation.html - // Foo is an example field of FirstMate. Edit firstmate_types.go to remove/update - Foo string `json:"foo,omitempty"` + // foo is an example field of FirstMate. Edit firstmate_types.go to remove/update + // +optional + Foo *string `json:"foo,omitempty"` } // FirstMateStatus defines the observed state of FirstMate. @@ -41,18 +44,26 @@ type FirstMateStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status -// FirstMate is the Schema for the firstmates API. +// FirstMate is the Schema for the firstmates API type FirstMate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + + // spec defines the desired state of FirstMate + // +required + Spec FirstMateSpec `json:"spec"` - Spec FirstMateSpec `json:"spec,omitempty"` - Status FirstMateStatus `json:"status,omitempty"` + // status defines the observed state of FirstMate + // +optional + Status FirstMateStatus `json:"status,omitempty,omitzero"` } // +kubebuilder:object:root=true -// FirstMateList contains a list of FirstMate. +// FirstMateList contains a list of FirstMate type FirstMateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v4/api/v2/zz_generated.deepcopy.go b/testdata/project-v4/api/v2/zz_generated.deepcopy.go index 894c5541390..94ffd98db70 100644 --- a/testdata/project-v4/api/v2/zz_generated.deepcopy.go +++ b/testdata/project-v4/api/v2/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *FirstMate) DeepCopyInto(out *FirstMate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,11 @@ func (in *FirstMateList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) { *out = *in + if in.Foo != nil { + in, out := &in.Foo, &out.Foo + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec. diff --git a/testdata/project-v4/cmd/main.go b/testdata/project-v4/cmd/main.go index d2d1a54b6a3..952eaeb6680 100644 --- a/testdata/project-v4/cmd/main.go +++ b/testdata/project-v4/cmd/main.go @@ -42,10 +42,7 @@ import ( crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" crewv2 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v2" "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/controller" - webhookappsv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" - webhookcertmanagerv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" - webhookcorev1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" - webhookcrewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" + webhookv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1" // +kubebuilder:scaffold:imports ) @@ -144,7 +141,7 @@ func main() { // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server // - https://book.kubebuilder.io/reference/metrics.html metricsServerOptions := metricsserver.Options{ BindAddress: metricsAddr, @@ -156,7 +153,7 @@ func main() { // FilterProvider is used to protect the metrics endpoint with authn/authz. // These configurations ensure that only authorized users and service accounts // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } @@ -211,7 +208,7 @@ func main() { os.Exit(1) } - if err = (&controller.CaptainReconciler{ + if err := (&controller.CaptainReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -220,12 +217,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcrewv1.SetupCaptainWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupCaptainWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Captain") os.Exit(1) } } - if err = (&controller.FirstMateReconciler{ + if err := (&controller.FirstMateReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -234,12 +231,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcrewv1.SetupFirstMateWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupFirstMateWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "FirstMate") os.Exit(1) } } - if err = (&controller.AdmiralReconciler{ + if err := (&controller.AdmiralReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -248,12 +245,12 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcrewv1.SetupAdmiralWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupAdmiralWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Admiral") os.Exit(1) } } - if err = (&controller.CertificateReconciler{ + if err := (&controller.CertificateReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { @@ -262,21 +259,21 @@ func main() { } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcertmanagerv1.SetupIssuerWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupIssuerWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Issuer") os.Exit(1) } } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupPodWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Pod") os.Exit(1) } } // nolint:goconst if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = webhookappsv1.SetupDeploymentWebhookWithManager(mgr); err != nil { + if err := webhookv1.SetupDeploymentWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "Deployment") os.Exit(1) } diff --git a/testdata/project-v4/config/crd/bases/crew.testproject.org_admirales.yaml b/testdata/project-v4/config/crd/bases/crew.testproject.org_admirales.yaml index 00f3075dc8e..bb74e3ce96a 100644 --- a/testdata/project-v4/config/crd/bases/crew.testproject.org_admirales.yaml +++ b/testdata/project-v4/config/crd/bases/crew.testproject.org_admirales.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: admirales.crew.testproject.org spec: group: crew.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Admiral is the Schema for the admirales API. + description: Admiral is the Schema for the admirales API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: AdmiralSpec defines the desired state of Admiral. + description: spec defines the desired state of Admiral properties: foo: - description: Foo is an example field of Admiral. Edit admiral_types.go + description: foo is an example field of Admiral. Edit admiral_types.go to remove/update type: string type: object status: - description: AdmiralStatus defines the observed state of Admiral. + description: status defines the observed state of Admiral type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4/config/crd/bases/crew.testproject.org_captains.yaml b/testdata/project-v4/config/crd/bases/crew.testproject.org_captains.yaml index c8cf3d723a0..438c614dd33 100644 --- a/testdata/project-v4/config/crd/bases/crew.testproject.org_captains.yaml +++ b/testdata/project-v4/config/crd/bases/crew.testproject.org_captains.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: captains.crew.testproject.org spec: group: crew.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Captain is the Schema for the captains API. + description: Captain is the Schema for the captains API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: CaptainSpec defines the desired state of Captain. + description: spec defines the desired state of Captain properties: foo: - description: Foo is an example field of Captain. Edit captain_types.go + description: foo is an example field of Captain. Edit captain_types.go to remove/update type: string type: object status: - description: CaptainStatus defines the observed state of Captain. + description: status defines the observed state of Captain type: object + required: + - spec type: object served: true storage: true diff --git a/testdata/project-v4/config/crd/bases/crew.testproject.org_firstmates.yaml b/testdata/project-v4/config/crd/bases/crew.testproject.org_firstmates.yaml index 8349258d19d..a03b2ffd6a1 100644 --- a/testdata/project-v4/config/crd/bases/crew.testproject.org_firstmates.yaml +++ b/testdata/project-v4/config/crd/bases/crew.testproject.org_firstmates.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: firstmates.crew.testproject.org spec: group: crew.testproject.org @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: FirstMate is the Schema for the firstmates API. + description: FirstMate is the Schema for the firstmates API properties: apiVersion: description: |- @@ -37,16 +37,18 @@ spec: metadata: type: object spec: - description: FirstMateSpec defines the desired state of FirstMate. + description: spec defines the desired state of FirstMate properties: foo: - description: Foo is an example field of FirstMate. Edit firstmate_types.go + description: foo is an example field of FirstMate. Edit firstmate_types.go to remove/update type: string type: object status: - description: FirstMateStatus defines the observed state of FirstMate. + description: status defines the observed state of FirstMate type: object + required: + - spec type: object served: true storage: true @@ -55,7 +57,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: FirstMate is the Schema for the firstmates API. + description: FirstMate is the Schema for the firstmates API properties: apiVersion: description: |- @@ -75,16 +77,18 @@ spec: metadata: type: object spec: - description: FirstMateSpec defines the desired state of FirstMate. + description: spec defines the desired state of FirstMate properties: foo: - description: Foo is an example field of FirstMate. Edit firstmate_types.go + description: foo is an example field of FirstMate. Edit firstmate_types.go to remove/update type: string type: object status: - description: FirstMateStatus defines the observed state of FirstMate. + description: status defines the observed state of FirstMate type: object + required: + - spec type: object served: true storage: false diff --git a/testdata/project-v4/config/default/kustomization.yaml b/testdata/project-v4/config/default/kustomization.yaml index e0fad41c4fc..e858e214b69 100644 --- a/testdata/project-v4/config/default/kustomization.yaml +++ b/testdata/project-v4/config/default/kustomization.yaml @@ -22,7 +22,7 @@ resources: # crd/kustomization.yaml - ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus # [METRICS] Expose the controller manager metrics service. @@ -56,7 +56,7 @@ patches: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # Uncomment the following replacements to add the cert-manager CA injection annotations -#replacements: +replacements: # - source: # Uncomment the following block to enable certificates for metrics # kind: Service # version: v1 @@ -86,7 +86,7 @@ patches: # delimiter: '.' # index: 0 # create: true -# + # - source: # kind: Service # version: v1 @@ -116,137 +116,137 @@ patches: # delimiter: '.' # index: 1 # create: true -# -# - source: # Uncomment the following block if you have any webhook -# kind: Service -# version: v1 -# name: webhook-service -# fieldPath: .metadata.name # Name of the service -# targets: -# - select: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# 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 -# name: serving-cert -# fieldPaths: -# - .spec.dnsNames.0 -# - .spec.dnsNames.1 -# options: -# delimiter: '.' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) -# 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 -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: -# - select: -# kind: ValidatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# 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 -# fieldPath: .metadata.name -# targets: -# - select: -# kind: MutatingWebhookConfiguration -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true -# -# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.namespace # Namespace of the certificate CR -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: firstmates.crew.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 0 -# create: true + + - source: # Uncomment the following block if you have any webhook + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name # Name of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + 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 + name: serving-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true + + - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) + 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 + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + + - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.namespace # Namespace of the certificate CR + targets: + - select: + kind: MutatingWebhookConfiguration + 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 + fieldPath: .metadata.name + targets: + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + + - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.namespace # Namespace of the certificate CR + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: firstmates.crew.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionns -# - source: -# kind: Certificate -# group: cert-manager.io -# version: v1 -# name: serving-cert -# fieldPath: .metadata.name -# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. -# - select: -# kind: CustomResourceDefinition -# name: firstmates.crew.testproject.org -# fieldPaths: -# - .metadata.annotations.[cert-manager.io/inject-ca-from] -# options: -# delimiter: '/' -# index: 1 -# create: true + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert + fieldPath: .metadata.name + targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. + - select: + kind: CustomResourceDefinition + name: firstmates.crew.testproject.org + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true # +kubebuilder:scaffold:crdkustomizecainjectionname diff --git a/testdata/project-v4/config/manager/manager.yaml b/testdata/project-v4/config/manager/manager.yaml index eae99c5dab5..90cbcc10c8f 100644 --- a/testdata/project-v4/config/manager/manager.yaml +++ b/testdata/project-v4/config/manager/manager.yaml @@ -67,6 +67,7 @@ spec: name: manager ports: [] securityContext: + readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: diff --git a/testdata/project-v4/config/rbac/kustomization.yaml b/testdata/project-v4/config/rbac/kustomization.yaml index ee1ae3d578e..6aef64a7bba 100644 --- a/testdata/project-v4/config/rbac/kustomization.yaml +++ b/testdata/project-v4/config/rbac/kustomization.yaml @@ -20,7 +20,7 @@ resources: - metrics_reader_role.yaml # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are -# not used by the {{ .ProjectName }} itself. You can comment the following lines +# not used by the project-v4 itself. You can comment the following lines # if you do not want those helpers be installed with your Project. - admiral_admin_role.yaml - admiral_editor_role.yaml diff --git a/testdata/project-v4/dist/install.yaml b/testdata/project-v4/dist/install.yaml index 1e2a28a9bf8..39f64d8e3ca 100644 --- a/testdata/project-v4/dist/install.yaml +++ b/testdata/project-v4/dist/install.yaml @@ -11,7 +11,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: admirales.crew.testproject.org spec: group: crew.testproject.org @@ -25,7 +25,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Admiral is the Schema for the admirales API. + description: Admiral is the Schema for the admirales API properties: apiVersion: description: |- @@ -45,16 +45,18 @@ spec: metadata: type: object spec: - description: AdmiralSpec defines the desired state of Admiral. + description: spec defines the desired state of Admiral properties: foo: - description: Foo is an example field of Admiral. Edit admiral_types.go + description: foo is an example field of Admiral. Edit admiral_types.go to remove/update type: string type: object status: - description: AdmiralStatus defines the observed state of Admiral. + description: status defines the observed state of Admiral type: object + required: + - spec type: object served: true storage: true @@ -65,7 +67,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: captains.crew.testproject.org spec: group: crew.testproject.org @@ -79,7 +81,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: Captain is the Schema for the captains API. + description: Captain is the Schema for the captains API properties: apiVersion: description: |- @@ -99,16 +101,18 @@ spec: metadata: type: object spec: - description: CaptainSpec defines the desired state of Captain. + description: spec defines the desired state of Captain properties: foo: - description: Foo is an example field of Captain. Edit captain_types.go + description: foo is an example field of Captain. Edit captain_types.go to remove/update type: string type: object status: - description: CaptainStatus defines the observed state of Captain. + description: status defines the observed state of Captain type: object + required: + - spec type: object served: true storage: true @@ -119,7 +123,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert + controller-gen.kubebuilder.io/version: v0.18.0 name: firstmates.crew.testproject.org spec: conversion: @@ -143,7 +148,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: FirstMate is the Schema for the firstmates API. + description: FirstMate is the Schema for the firstmates API properties: apiVersion: description: |- @@ -163,16 +168,18 @@ spec: metadata: type: object spec: - description: FirstMateSpec defines the desired state of FirstMate. + description: spec defines the desired state of FirstMate properties: foo: - description: Foo is an example field of FirstMate. Edit firstmate_types.go + description: foo is an example field of FirstMate. Edit firstmate_types.go to remove/update type: string type: object status: - description: FirstMateStatus defines the observed state of FirstMate. + description: status defines the observed state of FirstMate type: object + required: + - spec type: object served: true storage: true @@ -181,7 +188,7 @@ spec: - name: v2 schema: openAPIV3Schema: - description: FirstMate is the Schema for the firstmates API. + description: FirstMate is the Schema for the firstmates API properties: apiVersion: description: |- @@ -201,16 +208,18 @@ spec: metadata: type: object spec: - description: FirstMateSpec defines the desired state of FirstMate. + description: spec defines the desired state of FirstMate properties: foo: - description: Foo is an example field of FirstMate. Edit firstmate_types.go + description: foo is an example field of FirstMate. Edit firstmate_types.go to remove/update type: string type: object status: - description: FirstMateStatus defines the observed state of FirstMate. + description: status defines the observed state of FirstMate type: object + required: + - spec type: object served: true storage: false @@ -715,6 +724,7 @@ spec: capabilities: drop: - ALL + readOnlyRootFilesystem: true volumeMounts: - mountPath: /tmp/k8s-webhook-server/serving-certs name: webhook-certs @@ -730,9 +740,56 @@ spec: secret: secretName: webhook-server-cert --- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4 + name: project-v4-metrics-certs + namespace: project-v4-system +spec: + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-selfsigned-issuer + secretName: metrics-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4 + name: project-v4-serving-cert + namespace: project-v4-system +spec: + dnsNames: + - project-v4-webhook-service.project-v4-system.svc + - project-v4-webhook-service.project-v4-system.svc.cluster.local + issuerRef: + kind: Issuer + name: project-v4-selfsigned-issuer + secretName: webhook-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: project-v4 + name: project-v4-selfsigned-issuer + namespace: project-v4-system +spec: + selfSigned: {} +--- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + annotations: + cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert name: project-v4-mutating-webhook-configuration webhooks: - admissionReviewVersions: @@ -839,6 +896,8 @@ webhooks: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: + annotations: + cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert name: project-v4-validating-webhook-configuration webhooks: - admissionReviewVersions: diff --git a/testdata/project-v4/go.mod b/testdata/project-v4/go.mod index 53f65bc1b0d..ef3693e5798 100644 --- a/testdata/project-v4/go.mod +++ b/testdata/project-v4/go.mod @@ -1,23 +1,20 @@ module sigs.k8s.io/kubebuilder/testdata/project-v4 -go 1.23.0 - -godebug default=go1.23 +go 1.24.0 require ( - github.com/cert-manager/cert-manager v1.17.1 + github.com/cert-manager/cert-manager v1.18.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 - k8s.io/api v0.32.1 - k8s.io/apimachinery v0.32.1 - k8s.io/client-go v0.32.1 - sigs.k8s.io/controller-runtime v0.20.4 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 + sigs.k8s.io/controller-runtime v0.21.0 ) require ( cel.dev/expr v0.19.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -36,27 +33,24 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.1 // indirect + github.com/google/cel-go v0.23.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.9.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.20.5 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -74,31 +68,32 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.28.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect google.golang.org/grpc v1.69.2 // indirect - google.golang.org/protobuf v1.36.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect - k8s.io/apiserver v0.32.1 // indirect - k8s.io/component-base v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.33.0 // indirect + k8s.io/apiserver v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/testdata/project-v4/internal/controller/admiral_controller.go b/testdata/project-v4/internal/controller/admiral_controller.go index 95f6ab13fe2..f0df641e798 100644 --- a/testdata/project-v4/internal/controller/admiral_controller.go +++ b/testdata/project-v4/internal/controller/admiral_controller.go @@ -45,7 +45,7 @@ type AdmiralReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *AdmiralReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4/internal/controller/captain_controller.go b/testdata/project-v4/internal/controller/captain_controller.go index ab505b28332..62f0b62b3b3 100644 --- a/testdata/project-v4/internal/controller/captain_controller.go +++ b/testdata/project-v4/internal/controller/captain_controller.go @@ -45,7 +45,7 @@ type CaptainReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4/internal/controller/certificate_controller.go b/testdata/project-v4/internal/controller/certificate_controller.go index c122e571896..d47277f8443 100644 --- a/testdata/project-v4/internal/controller/certificate_controller.go +++ b/testdata/project-v4/internal/controller/certificate_controller.go @@ -44,7 +44,7 @@ type CertificateReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4/internal/controller/firstmate_controller.go b/testdata/project-v4/internal/controller/firstmate_controller.go index bfb078b3967..f62278763a9 100644 --- a/testdata/project-v4/internal/controller/firstmate_controller.go +++ b/testdata/project-v4/internal/controller/firstmate_controller.go @@ -45,7 +45,7 @@ type FirstMateReconciler struct { // the user. // // For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *FirstMateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) diff --git a/testdata/project-v4/internal/webhook/v1/admiral_webhook.go b/testdata/project-v4/internal/webhook/v1/admiral_webhook.go index 3099166b584..a30f4394364 100644 --- a/testdata/project-v4/internal/webhook/v1/admiral_webhook.go +++ b/testdata/project-v4/internal/webhook/v1/admiral_webhook.go @@ -55,7 +55,7 @@ type AdmiralCustomDefaulter struct { var _ webhook.CustomDefaulter = &AdmiralCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Admiral. -func (d *AdmiralCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *AdmiralCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { admiral, ok := obj.(*crewv1.Admiral) if !ok { diff --git a/testdata/project-v4/internal/webhook/v1/captain_webhook.go b/testdata/project-v4/internal/webhook/v1/captain_webhook.go index e8f5f95666c..f6d80fb6562 100644 --- a/testdata/project-v4/internal/webhook/v1/captain_webhook.go +++ b/testdata/project-v4/internal/webhook/v1/captain_webhook.go @@ -57,7 +57,7 @@ type CaptainCustomDefaulter struct { var _ webhook.CustomDefaulter = &CaptainCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain. -func (d *CaptainCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *CaptainCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { captain, ok := obj.(*crewv1.Captain) if !ok { @@ -87,7 +87,7 @@ type CaptainCustomValidator struct { var _ webhook.CustomValidator = &CaptainCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain. -func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *CaptainCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { captain, ok := obj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object but got %T", obj) @@ -100,7 +100,7 @@ func (v *CaptainCustomValidator) ValidateCreate(ctx context.Context, obj runtime } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain. -func (v *CaptainCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *CaptainCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { captain, ok := newObj.(*crewv1.Captain) if !ok { return nil, fmt.Errorf("expected a Captain object for the newObj but got %T", newObj) diff --git a/testdata/project-v4/internal/webhook/v1/deployment_webhook.go b/testdata/project-v4/internal/webhook/v1/deployment_webhook.go index d838f845377..2fbc30f48e0 100644 --- a/testdata/project-v4/internal/webhook/v1/deployment_webhook.go +++ b/testdata/project-v4/internal/webhook/v1/deployment_webhook.go @@ -56,7 +56,7 @@ type DeploymentCustomDefaulter struct { var _ webhook.CustomDefaulter = &DeploymentCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Deployment. -func (d *DeploymentCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *DeploymentCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { deployment, ok := obj.(*appsv1.Deployment) if !ok { @@ -86,7 +86,7 @@ type DeploymentCustomValidator struct { var _ webhook.CustomValidator = &DeploymentCustomValidator{} // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Deployment. -func (v *DeploymentCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { +func (v *DeploymentCustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { deployment, ok := obj.(*appsv1.Deployment) if !ok { return nil, fmt.Errorf("expected a Deployment object but got %T", obj) @@ -99,7 +99,7 @@ func (v *DeploymentCustomValidator) ValidateCreate(ctx context.Context, obj runt } // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Deployment. -func (v *DeploymentCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { +func (v *DeploymentCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { deployment, ok := newObj.(*appsv1.Deployment) if !ok { return nil, fmt.Errorf("expected a Deployment object for the newObj but got %T", newObj) diff --git a/testdata/project-v4/internal/webhook/v1/issuer_webhook.go b/testdata/project-v4/internal/webhook/v1/issuer_webhook.go index 0577f525cc2..1ce37cdf796 100644 --- a/testdata/project-v4/internal/webhook/v1/issuer_webhook.go +++ b/testdata/project-v4/internal/webhook/v1/issuer_webhook.go @@ -54,7 +54,7 @@ type IssuerCustomDefaulter struct { var _ webhook.CustomDefaulter = &IssuerCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Issuer. -func (d *IssuerCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *IssuerCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { issuer, ok := obj.(*certmanagerv1.Issuer) if !ok { diff --git a/testdata/project-v4/internal/webhook/v1/pod_webhook.go b/testdata/project-v4/internal/webhook/v1/pod_webhook.go index 99af588c106..16e5b3d80dc 100644 --- a/testdata/project-v4/internal/webhook/v1/pod_webhook.go +++ b/testdata/project-v4/internal/webhook/v1/pod_webhook.go @@ -54,7 +54,7 @@ type PodCustomDefaulter struct { var _ webhook.CustomDefaulter = &PodCustomDefaulter{} // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Pod. -func (d *PodCustomDefaulter) Default(ctx context.Context, obj runtime.Object) error { +func (d *PodCustomDefaulter) Default(_ context.Context, obj runtime.Object) error { pod, ok := obj.(*corev1.Pod) if !ok { diff --git a/testdata/project-v4/test/e2e/e2e_suite_test.go b/testdata/project-v4/test/e2e/e2e_suite_test.go index d9ba77ee512..5f6125f385e 100644 --- a/testdata/project-v4/test/e2e/e2e_suite_test.go +++ b/testdata/project-v4/test/e2e/e2e_suite_test.go @@ -43,7 +43,7 @@ var ( ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. +// temporary environment to validate project changes with the purpose of being used in CI jobs. // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs // CertManager. func TestE2E(t *testing.T) { diff --git a/testdata/project-v4/test/e2e/e2e_test.go b/testdata/project-v4/test/e2e/e2e_test.go index b79f734c692..cda561df0ee 100644 --- a/testdata/project-v4/test/e2e/e2e_test.go +++ b/testdata/project-v4/test/e2e/e2e_test.go @@ -221,6 +221,7 @@ var _ = Describe("Manager", Ordered, func() { "command": ["/bin/sh", "-c"], "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], "securityContext": { + "readOnlyRootFilesystem": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] @@ -232,7 +233,7 @@ var _ = Describe("Manager", Ordered, func() { } } }], - "serviceAccount": "%s" + "serviceAccountName": "%s" } }`, token, metricsServiceName, namespace, serviceAccountName)) _, err = utils.Run(cmd) diff --git a/testdata/project-v4/test/utils/utils.go b/testdata/project-v4/test/utils/utils.go index 9419db9a8d0..1d6164b84bc 100644 --- a/testdata/project-v4/test/utils/utils.go +++ b/testdata/project-v4/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck ) const ( @@ -46,15 +46,15 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) output, err := cmd.CombinedOutput() if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return string(output), nil @@ -195,9 +195,9 @@ func GetNonEmptyLines(output string) []string { func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { - return wd, err + return wd, fmt.Errorf("failed to get current working directory: %w", err) } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } @@ -208,19 +208,19 @@ func UncommentCode(filename, target, prefix string) error { // nolint:gosec content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to output: %w", err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -228,24 +228,27 @@ func UncommentCode(filename, target, prefix string) error { return nil } for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } - if _, err := out.WriteString("\n"); err != nil { - return err + if _, err = out.WriteString("\n"); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } + // false positive // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil }