[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: Bug Report\ndescription: Problems and issues with code or docs\nlabels:\n- kind/bug\nbody: \n- type: markdown\n  attributes:\n    value: |\n      :warning: **Stop!** :warning:\n\n      * If this is an issue with some sort of **runtime mechanics**,\n        it probably belongs in [controller-runtime][cr-issue] instead\n\n      * If this is an issue with **CRD generation**, webhook config generation,\n        or deepcopy generation, it probably belongs in [controller-tools][ct-issue].\n\n      * If this is an issue with **scaffolding**, or is definitely a\n        cross-repository effort, it probably belongs here.  Feel free to continue :wink:\n\n      [cr-issue]: https://github.com/kubernetes-sigs/controller-runtime/issues/new\n      [ct-issue]: https://github.com/kubernetes-sigs/controller-tools/issues/new\n\n- type: markdown\n  attributes:\n    value: |\n      # Hiya!  Welcome to Kubebuilder!\n      \n      For a smooth issue process, try to answer the following questions.\n      Don't worry if they're not all applicable; just try to include what you can :smile:\n\n      If you need to include code snippets or logs, please put them in fenced code\n      blocks, and if they're really long, use the [`<details>` tag][mdn-details], like:\n\n      <details>\n\n      <summary>Code & details examples</summary>\n      \n      `````markdown\n      Some code written in Go:\n\n      ```go\n      type Manager struct {\n        // FixTheBug removes all bugs from controller-runtime\n        FixTheBug bool\n      }\n      ```\n\n      <details>\n\n      <summary>Some really long logs</summary>\n\n      ```\n      ok\n      ok\n      ok\n      SHOOT A BUG HAPPENS HERE OH NO\n      ok\n      ok\n      done\n      ```\n\n      </details>\n\n      `````\n\n      [mdn-details]: ://developer.mozilla.org/en-US/docs/Web/HTML/Element/details \n\n- type: textarea\n  attributes:\n    label: What broke? What's expected?\n    description: |\n      Describe what didn't go the way you thought it would.\n      Please include *full* & *exact* error messages if you can!\n\n      If you have an idea of what went wrong, feel free to include that\n      as well.\n  validations: {required: true}\n \n- type: textarea\n  attributes:\n    label: Reproducing this issue\n    description:\n      If you have simple reproduction steps, or a minimal reproducer code\n      snippet, please include it here.\n\n      If they're already described above, no need to duplicate it here :smile:.\n\n- type: markdown\n  attributes:\n    value: |\n      ## What versions are you using?\n      \n      Please specify the relevant versions and sources for the pieces of\n      kubebuilder that you're using.  The more details you can provide, the\n      better.\n\n- type: input\n  id: cli-version\n  attributes:\n    label: KubeBuilder (CLI) Version\n    description: \"use `kubebuilder version` to find this out\"\n  validations:\n    required: true\n\n# project-version & plugin versions are not required for issues with initial scaffolding\n- type: input\n  id: project-version\n  attributes:\n    label: PROJECT version\n    description: \"look for the `version` field in your PROJECT file to find this\"\n\n- type: textarea\n  attributes:\n    label: Plugin versions\n    description: \"list the values of the `layout` field in your PROJECT file, if on KubeBuilder v3+\"\n    render: yaml\n\n- type: textarea\n  attributes:\n    label: Other versions\n    description: |\n      Often times, the following pieces of information are relevant:\n      - Go version (`go version`)\n      - controller-runtime & controller-tools version (check your `go.mod` file)\n      - Kubernetes & kubectl versions (run `kubectl version` against your api server)\n\n- type: dropdown\n  attributes: \n    label: \"Extra Labels\"\n    description: |\n      If this is *also* a documentation request, etc, please select that below.\n    multiple: true\n    options:\n    - \"/kind documentation\"\n    - \"/kind feature\"\n    - \"/kind regression\"\n    - \"/kind deprecation\"\n    - \"/kind cleanup\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# allow free form issues as an escape hatch.  This can be taken away if people abuse it ;-)\nblank_issues_enabled: true\n\n# link to CR and CT for easier access\ncontact_links:\n- name: Runtime Issues\n  url: https://github.com/kubernetes-sigs/controller-runtime/issues/new\n  about: Runtime issues generally belong in the controller-runtime repository\n\n- name: CRD/Webhook/Deepcopy Generation Issues\n  url: https://github.com/kubernetes-sigs/controller-tools/issues/new\n  about: YAML & Go generation issues generally belong in the controller-tools repository\n\n- name: Support Questions\n  url: https://github.com/kubernetes-sigs/kubebuilder/discussions/new\n  about: Need support & not sure if this a bug?  You can ask questions in Slack or GitHub discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project or its docs\nlabels:\n- kind/feature\nbody:\n- type: markdown\n  attributes:\n    value: |\n      :warning: **Stop!** :warning:\n\n      * If this is an issue with some sort of **runtime mechanics**,\n        it probably belongs in [controller-runtime][cr-issue] instead\n\n      * If this is an issue with **CRD generation**, webhook config generation,\n        or deepcopy generation, it probably belongs in [controller-tools][ct-issue].\n\n      * If this is an issue with **scaffolding**, or is definitely a\n        cross-repository effort, it probably belongs here.  Feel free to continue :wink:\n\n      [cr-issue]: https://github.com/kubernetes-sigs/controller-runtime/issues/new\n      [ct-issue]: https://github.com/kubernetes-sigs/controller-tools/issues/new\n\n- type: markdown\n  attributes:\n    value: |\n      # Hiya!  Welcome to Kubebuilder!\n\n      For a smooth issue process, try to answer the following questions.\n      Don't worry if they're not all applicable; just try to include what you can :smile:\n\n      If you need to include code snippets or logs, please put them in fenced code\n      blocks, and if they're really long, use the [`<details>` tag][mdn-details], like:\n\n      <details>\n\n      <summary>Code & details examples</summary>\n\n      `````markdown\n      Some code written in Go:\n\n      ```go\n      type Manager struct {\n        // FixTheBug removes all bugs from controller-runtime\n        FixTheBug bool\n      }\n      ```\n\n      <details>\n\n      <summary>Some really long logs</summary>\n\n      ```\n      ok\n      ok\n      ok\n      SHOOT A BUG HAPPENS HERE OH NO\n      ok\n      ok\n      done\n      ```\n\n      </details>\n\n      `````\n\n      [mdn-details]: ://developer.mozilla.org/en-US/docs/Web/HTML/Element/details\n\n- type: textarea\n  attributes:\n    label: What do you want to happen?\n    description: |\n      Describe the feature you want, and what the motivation is.\n\n      What are your use cases?  Why should we do this?\n\n      Does it require a particular Kubernetes version?\n\n      Is there currently another issue associated with this (use github syntax\n      like `#xyz` to link to it)?\n  validations: {required: true}\n\n- type: dropdown\n  attributes:\n    label: \"Extra Labels\"\n    description: |\n      If this is *also* a documentation request, etc, please select that below.\n    multiple: true\n    options:\n    - \"/kind documentation\"\n    - \"/kind regression\"\n    - \"/kind deprecation\"\n    - \"/kind cleanup\"\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n\nHiya!  Welcome to Kubebuilder!  For a smooth PR process, please ensure\nthat you include the following information:\n\n* a description of the change\n* the motivation for the change\n* what issue it fixes, if any, in GitHub syntax (e.g. Fixes #XYZ)\n\nBoth the description and motivation may reference other issues and PRs,\nbut should be mostly understandable without following the links (e.g. when\nreading the git commit log).\n\nPlease don't @-mention people in PR or commit messages (do so in an\nadditional comment).\n\nplease add an icon to the title of this PR depending on the type:\n\n- ⚠ (:warning:): breaking\n- ✨ (:sparkles:): new non-breaking feature\n- 🐛 (:bug:): bugfix\n- 📖 (:book:): documentation\n- 🌱 (:seedling:): infrastructure/other\n\nSee https://sigs.k8s.io/kubebuilder-release-tools for more information.\n\n**PLEASE REMOVE THIS COMMENT BLOCK BEFORE SUBMITTING THE PR** (the bits\nbetween the arrows)\n\n-->\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Security Announcements\n\nJoin the [kubernetes-security-announce] group for security and vulnerability announcements related to the Kubernetes ecosystem.\n\nYou can also subscribe to an RSS feed of these announcements using [this link][kubernetes-security-announce-rss].\n\n## Reporting a Vulnerability\n\nInstructions for reporting a vulnerability can be found on the [Kubernetes Security and Disclosure Information] page.\n\n## Supported Versions\n\nKubebuilder is tested against the latest three Kubernetes releases, in alignment with the [Kubernetes version and version skew support policy](https://kubernetes.io/docs/setup/release/version-skew-policy/).\n\nHowever, each version is only tested with the dependencies used for its release. For detailed information, please refer to the [compatibility and support policy on GitHub][compatibility-policy].\n\n## Release Policy\n\nKubebuilder maintains a policy of releasing updates for the latest CLI version (currently v4). Older versions (v1, v2, v3) are no longer supported, and no releases will be produced for them. It is recommended to ensure that any project scaffolded by Kubebuilder remains aligned with the latest release.\n\n## Automated Vulnerability Scanning\n\nKubebuilder employs automated scanning via Dependabot and GitHub Actions within its CI/CD pipeline. This process detects vulnerabilities in dependencies and configurations, generating daily or weekly reports prioritized for the latest supported versions.\n\n- **Dependabot Configuration**: You can review the setup in `.github/dependabot.yml`.\n- **Security Checks**: Security checks are enabled in the Kubebuilder repository settings.\n- **Code Scanning**: The `.github/workflows/codeql.yml` workflow scans the `master` and `book-v4` branches, which typically contain the latest release code. Other release branches may not be scanned.\n\n## Production-Grade Security\n\nProjects generated by Kubebuilder are designed for ease of development and are **not** configured with production-grade security settings. For example, default configurations do not enable cert-manager or perform proper certificate validation, which may not be suitable for production environments. Ensure that you make the necessary adjustments to security settings before releasing your solution for production.\n\n[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce\n[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50\n[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions\n[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability\n[compatibility-policy]: ./../README.md#versions-compatibility-and-supportability\n[project-upgrade-assistant]: https://book.kubebuilder.io/reference/rescaffold\n[testdata-directory]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata\n[kubebuilder-releases]: https://github.com/kubernetes-sigs/kubebuilder/releases\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    # Workflow files stored in the\n    # default location of `.github/workflows`\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    commit-message:\n      prefix: \":seedling:\"\n    labels:\n      - \"ok-to-test\"\n\n  # Maintain dependencies for go\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    commit-message:\n      prefix: \":seedling:\"\n    labels:\n      - \"ok-to-test\"\n\n  # Maintain dependencies for go\n  - package-ecosystem: \"gomod\"\n    directory: \"/testdata/project-v4\"\n    schedule:\n      interval: \"daily\"\n    commit-message:\n      prefix: \":seedling:\"\n    labels:\n      - \"ok-to-test\"\n\n  # Maintain dependencies for dockerfile scaffold in the projects\n  - package-ecosystem: docker\n    directory: \"testdata/project-v4\"\n    schedule:\n      interval: daily\n    commit-message:\n      prefix: \":seedling:\"\n\n  # Maintain dependencies for go in external plugin sample\n  - package-ecosystem: \"gomod\"\n    directory: \"docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1\"\n    schedule:\n      interval: \"daily\"\n    commit-message:\n      prefix: \":book:\"\n    labels:\n      - \"ok-to-test\"\n"
  },
  {
    "path": ".github/instructions/kubebuilder.instructions.md",
    "content": "See [AGENTS.md](../../AGENTS.md) for AI agent guidelines.\n"
  },
  {
    "path": ".github/workflows/apidiff.yml",
    "content": "name: APIDiff\n\non:\n  push:\n    paths-ignore:\n      - '**/*.md'\n  pull_request:\n    paths-ignore:\n      - '**/*.md'\n\njobs:\n  go-apidiff:\n    name: Verify API differences\n    runs-on: ubuntu-latest\n    # Pull requests from different repository only trigger this checks\n    if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Execute go-apidiff\n        uses: joelanford/go-apidiff@v0.8.3\n        with:\n          compare-imports: true\n          print-compatible: true\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"CodeQL Advanced\"\n\non:\n  # We are checking both `master` and `book-v4` branches:\n  # - `master` represents the latest development work.\n  # - `book-v4` is the latest stable release branch, which contains the latest published code,\n  #   ensuring that any issues in production are identified and addressed promptly.\n  schedule:\n    - cron: '30 20 * * 1'  # Runs every Monday at 8:30 PM\n\njobs:\n  analyze:\n    name: Analyze Go\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      packages: read\n      actions: read\n      contents: read\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Build and install Kubebuilder CLI\n        run: make install\n\n      # Preparing the project-v4 sample for CodeQL analysis:\n      # - `go mod tidy` ensures dependencies are fully resolved.\n      # - `make manifests` generates required manifests for a complete project structure.\n      # - `make build` builds the project code, ensuring all components are ready for CodeQL analysis.\n      - name: Build project-v4 sample project\n        run: |\n          cd testdata/project-v4\n          go mod tidy\n          echo 'Running build commands for Go in project-v4'\n          make manifests\n          make build\n\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: go\n          build-mode: autobuild\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n        with:\n          category: \"/language:go\"\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: Coverage\n\non:\n  push:\n    paths-ignore: ['**/*.md']\n  pull_request:\n    paths-ignore: ['**/*.md']\n\njobs:\n  coverage:\n    runs-on: ubuntu-latest\n    if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Remove pre-installed kustomize\n        run: sudo rm -f /usr/local/bin/kustomize\n\n      - name: Install Kubebuilder\n        run: make install\n\n      - name: Run tests with coverage\n        run: make test-coverage\n\n      - name: Upload coverage to Coveralls\n        uses: shogo82148/actions-goveralls@v1\n        with:\n          path-to-profile: coverage-all.out\n"
  },
  {
    "path": ".github/workflows/cross-platform-tests.yml",
    "content": "name: Cross-Platform Tests\n\n# Trigger the workflow on pull requests and direct pushes to any branch\non:\n  push:\n    paths-ignore:\n      - '**/*.md'\n  pull_request:\n    paths-ignore:\n      - '**/*.md'\n\njobs:\n  test:\n    name: ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n    # Pull requests from the same repository won't trigger this checks as they were already triggered by the push\n    if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      # This step is needed as the following one tries to remove\n      # kustomize for each test but has no permission to do so\n      - name: Remove pre-installed kustomize\n        run: sudo rm -f /usr/local/bin/kustomize\n\n      - name: Unit Tests\n        run: make test-unit\n\n      - name: Run Testdata\n        run: make test-testdata\n"
  },
  {
    "path": ".github/workflows/external-plugin.yml",
    "content": "name: External Plugin\n\non:\n  push:\n    paths:\n      - 'pkg/'\n      - 'docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin'\n      - '.github/workflows/external-plugin.yml'\n  pull_request:\n    paths:\n      - 'pkg/'\n      - 'docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin'\n      - '.github/workflows/external-plugin.yml'\n\njobs:\n  external:\n    name: Verify external plugin\n    runs-on: ubuntu-latest\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Run tests\n        run: make test-external-plugin\n\n"
  },
  {
    "path": ".github/workflows/legacy-webhook-path.yml",
    "content": "# This test ensure that the legacy webhook path\n# still working. The option is deprecated\n# and should be removed when we no longer need\n# to support go/v4 plugin.\nname: Legacy Webhook Path\n\non:\n  push:\n    paths:\n      - 'testdata/**'\n      - '.github/workflows/legacy-webhook-path.yml'\n  pull_request:\n    paths:\n      - 'testdata/**'\n      - '.github/workflows/legacy-webhook-path.yml'\n\njobs:\n  webhook-legacy-path:\n    name: Verify Legacy Webhook Path\n    runs-on: ubuntu-latest\n    # Pull requests from the same repository won't trigger this checks as they were already triggered by the push\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Run make test-legacy\n        run: make test-legacy\n\n"
  },
  {
    "path": ".github/workflows/lint-sample.yml",
    "content": "name: Lint Samples\n\non:\n  push:\n    paths:\n      - 'testdata/**'\n      - 'docs/book/src/**/testdata/**'\n      - '.github/workflows/lint-sample.yml'\n  pull_request:\n    paths:\n      - 'testdata/**'\n      - 'docs/book/src/**/testdata/**'\n      - '.github/workflows/lint-sample.yml'\n\njobs:\n  lint-samples:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        folder: [\n          \"testdata/project-v4\",\n          \"testdata/project-v4-with-plugins\",\n          \"testdata/project-v4-multigroup\",\n          \"docs/book/src/cronjob-tutorial/testdata/project\",\n          \"docs/book/src/getting-started/testdata/project\",\n          \"docs/book/src/multiversion-tutorial/testdata/project\"\n        ]\n    if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Prepare ${{ matrix.folder }}\n        working-directory: ${{ matrix.folder }}\n        run: go mod tidy\n      - name: Check linter configuration\n        working-directory: ${{ matrix.folder }}\n        run: make lint-config\n      - name: Run linter\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: v2.8.0\n          working-directory: ${{ matrix.folder }}\n      - name: Run linter via makefile target\n        working-directory: ${{ matrix.folder }}\n        run: make lint\n\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    paths-ignore:\n      - '**/*.md'\n  pull_request:\n    paths-ignore:\n      - '**/*.md'\n\njobs:\n  lint:\n    name: golangci-lint\n    runs-on: ubuntu-latest\n    # Pull requests from the same repository won't trigger this checks as they were already triggered by the push\n    if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: v2.8.0\n\n  yamllint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - name: Install Helm\n        run: make install-helm\n      - name: Run yamllint (YAML + Helm chart output 2-space indentation)\n        run: make yamllint\n      - name: Check sample permissions\n        run: make check-sample-permissions\n\n  license:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - name: Run license check\n        run: make test-license\n"
  },
  {
    "path": ".github/workflows/release-version-ci.yml",
    "content": "name: Test GoReleaser and CLI Version\n\non:\n  pull_request:\n    branches:\n      - master\n    paths:\n      - 'pkg/**'\n      - 'cmd/**'\n      - 'build/.goreleaser.yml'\n      - '.github/workflows/release-version-ci.yml'\n\njobs:\n  go-releaser-test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Clean dist directory\n        run: rm -rf dist || true\n\n      - name: Create temporary git tag\n        run: |\n          git tag v4.5.3-rc.1\n\n      - name: Install Syft to generate SBOMs\n        run: |\n          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b $HOME/bin\n          echo \"$HOME/bin\" >> $GITHUB_PATH\n          \n      - name: Run GoReleaser in mock mode using tag\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          version: v2.7.0\n          args: release --skip=publish --clean -f ./build/.goreleaser.yml\n\n      - name: Init project using built kubebuilder binary and check cliVersion\n        run: |\n          mkdir test-operator && cd test-operator\n          go mod init test-operator\n          chmod +x ../dist/kubebuilder_linux_$(go env GOARCH)_v1/kubebuilder\n          ../dist/kubebuilder_linux_$(go env GOARCH)_v1/kubebuilder init --domain example.com\n\n          echo \"PROJECT file content:\"\n          cat PROJECT\n\n          echo \"Verifying cliVersion value...\"\n          grep '^cliVersion: 4.5.3-rc.1$' PROJECT\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\non:\n  push:\n    tags:\n      - '*'\n\npermissions:\n  contents: write\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Fetch all tags\n        run: git fetch --force --tags\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Clean dist directory\n        run: rm -rf dist || true\n      - name: Install Syft to generate SBOMs\n        run: |\n          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b $HOME/bin\n          echo \"$HOME/bin\" >> $GITHUB_PATH\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          version: v2.7.0\n          args: release -f ./build/.goreleaser.yml\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Upload assets\n        uses: actions/upload-artifact@v7.0.0\n        with:\n          name: kubebuilder\n          path: dist/*\n          if-no-files-found: error\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "name: Scorecard supply-chain security\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  schedule:\n    - cron: \"20 15 * * 5\"\n  push:\n    branches: [\"master\"]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.\n    if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Needed to publish results and get a badge (see publish_results below).\n      id-token: write\n      # Uncomment the permissions below if installing in a private repository.\n      # contents: read\n      # actions: read\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.1\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecard on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n          # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore\n          # file_mode: git\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard (optional).\n      # Commenting out will disable upload of results to your repo's Code Scanning dashboard\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/spaces.yml",
    "content": "name: Trailing\n\non:\n  push:\n    paths:\n      - '**/*.md'\n  pull_request:\n    paths:\n      - '**/*.md'\n\njobs:\n  lint:\n    name: \"Check Trailing\"\n    runs-on: ubuntu-latest\n    # Pull requests from the same repository won't trigger this checks as they were already triggered by the push\n    if: (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository)\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Run check\n        run: make test-spaces\n"
  },
  {
    "path": ".github/workflows/test-alpha-generate.yml",
    "content": "name: Test Alpha Generate\n\non:\n  push:\n    paths:\n      - 'pkg/cli/alpha/**'\n      - '.github/workflows/test-alpha-generate.yml'\n  pull_request:\n    paths:\n      - 'pkg/cli/alpha/**'\n      - '.github/workflows/test-alpha-generate.yml'\n      \njobs:\n  unsupported:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Install dependencies and generate binary\n        run: make install\n\n      - name: Navigate to testdata/project-v4\n        run: cd testdata/project-v4\n\n      - name: Update PROJECT file\n        run: |\n          sed -i 's#go.kubebuilder.io/v4#go.kubebuilder.io/v3#g' testdata/project-v4/PROJECT\n\n      # Validate if help output is working and workaround to\n      # update the PROJECT file in memory to allow upgrade\n      # no longer supported layouts did not break the command options\n      - name: Validate help output for alpha generate\n        run: |\n          if kubebuilder alpha generate --help | grep -q \"kubebuilder alpha generate \\[flags\\]\"; then\n            echo \"Help output validated\"\n          else\n            echo \"Help output missing or invalid\"\n            exit 1\n          fi    \n\n      - name: Run kubebuilder alpha generate\n        run: |\n          cd testdata/project-v4 && kubebuilder alpha generate\n\n"
  },
  {
    "path": ".github/workflows/test-book.yml",
    "content": "name: E2E Book Samples\r\n\r\non:\r\n  push:\r\n    paths:\r\n      - 'docs/book/src/getting-started/testdata/project/**'\r\n      - 'docs/book/src/cronjob-tutorial/testdata/project/**'\r\n      - 'docs/book/src/multiversion-tutorial/testdata/project/**'\r\n      - '.github/workflows/test-e2e-book.yml'\r\n  pull_request:\r\n    paths:\r\n      - 'docs/book/src/getting-started/testdata/project/**'\r\n      - 'docs/book/src/cronjob-tutorial/testdata/project/**'\r\n      - 'docs/book/src/multiversion-tutorial/testdata/project/**'\r\n      - '.github/workflows/test-e2e-book.yml'\r\n\r\njobs:\r\n  e2e:\r\n    runs-on: ubuntu-latest\r\n    strategy:\r\n      fail-fast: true\r\n      matrix:\r\n        folder: [\r\n          \"docs/book/src/getting-started/testdata/project\",\r\n          \"docs/book/src/cronjob-tutorial/testdata/project\",\r\n          \"docs/book/src/multiversion-tutorial/testdata/project\"\r\n        ]\r\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\r\n    steps:\r\n      - name: Checkout repository\r\n        uses: actions/checkout@v6.0.2\r\n\r\n      - name: Setup Go\r\n        uses: actions/setup-go@v6\r\n        with:\r\n          go-version-file: go.mod\r\n\r\n      - name: Install the latest version of kind\r\n        run: |\r\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\r\n          chmod +x ./kind\r\n          sudo mv ./kind /usr/local/bin/kind\r\n\r\n      - name: Verify kind installation\r\n        run: kind version\r\n\r\n      - name: Create kind cluster\r\n        run: kind create cluster\r\n\r\n      - name: Running make test for ${{ matrix.folder }}\r\n        working-directory: ${{ matrix.folder }}\r\n        run: make test\r\n\r\n      - name: Running make test-e2e for ${{ matrix.folder }}\r\n        working-directory: ${{ matrix.folder }}\r\n        run: make test-e2e\r\n"
  },
  {
    "path": ".github/workflows/test-devcontainer.yml",
    "content": "name: Test DevContainer Image\n\non:\n  push:\n    paths:\n      - 'testdata/**'\n      - '.github/workflows/test-devcontainer.yml'\n      - 'pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go'\n  pull_request:\n    paths:\n      - 'testdata/**'\n      - '.github/workflows/test-devcontainer.yml'\n      - 'pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go'\n\njobs:\n  test-devcontainer:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Build and Run Dev Container Tests\n        uses: devcontainers/ci@v0.3\n        with:\n          subFolder: testdata/project-v4\n          runCmd: |\n            # Source bash-completion for all tests\n            source /usr/share/bash-completion/bash_completion\n            \n            # Verify Tools Installation\n            echo \"Verifying installed tools...\"\n            docker --version\n            kind version\n            kubebuilder version\n            kubectl version --client\n            go version\n            echo \"All required tools are installed\"\n            \n            # Verify common-utils feature is installed\n            echo \"Verifying common-utils feature...\"\n            if ! command -v zsh &> /dev/null; then\n              echo \"ERROR: common-utils feature not installed (zsh missing)\"\n              exit 1\n            fi\n            echo \"common-utils feature is installed\"\n            \n            # Verify bash-completion setup\n            echo \"Verifying bash-completion configuration...\"\n            if ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc; then\n              echo \"ERROR: bash-completion not configured in .bashrc\"\n              exit 1\n            fi\n            echo \"bash-completion is configured in .bashrc\"\n            \n            # Verify completion files exist\n            echo \"Verifying completion files...\"\n            for cmd in kubectl kind kubebuilder docker; do\n              if [ ! -f \"/usr/share/bash-completion/completions/${cmd}\" ]; then\n                echo \"ERROR: Completion file for ${cmd} not found\"\n                exit 1\n              fi\n              echo \"${cmd} completion file exists\"\n            done\n            echo \"All completion files are present\"\n            \n            # Test bash-completion works\n            echo \"Testing bash-completion functionality...\"\n            # Bash-completion uses lazy-loading, so manually source completion to test\n            source /usr/share/bash-completion/completions/kubectl\n            if ! complete -p kubectl &> /dev/null; then\n              echo \"ERROR: kubectl completions not loaded\"\n              exit 1\n            fi\n            echo \"Bash completions are working\"\n\n            # Test Docker-in-Docker\n            echo \"Testing Docker-in-Docker functionality...\"\n            docker ps\n            docker network ls\n            docker run --rm hello-world\n            echo \"Docker is working inside devcontainer\"\n\n            # Test Kubebuilder Workflow\n            echo \"Testing Kubebuilder development workflow...\"\n            go mod tidy\n            make all\n            echo \"make all passed\"\n            make docker-build IMG=controller:ci\n            echo \"make docker-build passed\"\n\n            # Test Kind Cluster Creation\n            echo \"Testing kind cluster creation...\"\n            kind create cluster --name test-cluster\n            kubectl cluster-info --context kind-test-cluster\n            kubectl get nodes\n            kind delete cluster --name test-cluster\n            echo \"Kind cluster creation and deletion successful\"\n\n"
  },
  {
    "path": ".github/workflows/test-e2e-samples.yml",
    "content": "name: E2E Testdata Sample\r\n\r\non:\r\n  push:\r\n    paths:\r\n      - 'testdata/**'\r\n      - '.github/workflows/test-e2e-samples.yml'\r\n  pull_request:\r\n    paths:\r\n      - 'testdata/**'\r\n      - '.github/workflows/test-e2e-samples.yml'\r\n\r\njobs:\r\n  e2e-tests-project-v4:\r\n    runs-on: ubuntu-latest\r\n    strategy:\r\n      fail-fast: true\r\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\r\n    steps:\r\n      - name: Checkout repository\r\n        uses: actions/checkout@v6.0.2\r\n\r\n      - name: Setup Go\r\n        uses: actions/setup-go@v6\r\n        with:\r\n          go-version-file: go.mod\r\n\r\n      - name: Install the latest version of kind\r\n        run: |\r\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\r\n          chmod +x ./kind\r\n          sudo mv ./kind /usr/local/bin/kind\r\n\r\n      - name: Verify kind installation\r\n        run: kind version\r\n\r\n      - name: Create kind cluster\r\n        run: kind create cluster\r\n\r\n      - name: Prepare project-v4\r\n        run: |\r\n          # Enable [METRICS-WITH-CERTS] by uncommenting the lines in kustomization.yaml\r\n          KUSTOMIZATION_FILE_PATH=\"testdata/project-v4/config/default/kustomization.yaml\"\r\n          sed -i '47,49s/^#//' $KUSTOMIZATION_FILE_PATH\r\n          cd testdata/project-v4/\r\n          go mod tidy\r\n\r\n      - name: Testing make test-e2e for project-v4\r\n        working-directory: testdata/project-v4/\r\n        run: |\r\n          make test-e2e\r\n\r\n  e2e-tests-project-v4-with-plugins:\r\n    runs-on: ubuntu-latest\r\n    strategy:\r\n      fail-fast: true\r\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\r\n    steps:\r\n      - name: Checkout repository\r\n        uses: actions/checkout@v6.0.2\r\n\r\n      - name: Setup Go\r\n        uses: actions/setup-go@v6\r\n        with:\r\n          go-version-file: go.mod\r\n\r\n      - name: Install the latest version of kind\r\n        run: |\r\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\r\n          chmod +x ./kind\r\n          sudo mv ./kind /usr/local/bin/kind\r\n\r\n      - name: Verify kind installation\r\n        run: kind version\r\n\r\n      - name: Create kind cluster\r\n        run: kind create cluster\r\n\r\n      - name: Prepare project-v4-with-plugins\r\n        run: |\r\n          cd testdata/project-v4-with-plugins/\r\n          go mod tidy\r\n\r\n      - name: Testing make test-e2e for project-v4-with-plugins\r\n        working-directory: testdata/project-v4-with-plugins/\r\n        run: |\r\n          make test-e2e\r\n\r\n  e2e-tests-project-v4-multigroup:\r\n    runs-on: ubuntu-latest\r\n    strategy:\r\n      fail-fast: true\r\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\r\n    steps:\r\n      - name: Checkout repository\r\n        uses: actions/checkout@v6.0.2\r\n\r\n      - name: Setup Go\r\n        uses: actions/setup-go@v6\r\n        with:\r\n          go-version-file: go.mod\r\n\r\n      - name: Install the latest version of kind\r\n        run: |\r\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\r\n          chmod +x ./kind\r\n          sudo mv ./kind /usr/local/bin/kind\r\n\r\n      - name: Verify kind installation\r\n        run: kind version\r\n\r\n      - name: Create kind cluster\r\n        run: kind create cluster\r\n\r\n      - name: Prepare project-v4-multigroup\r\n        run: |\r\n          cd testdata/project-v4-multigroup\r\n          go mod tidy\r\n\r\n      - name: Testing make test-e2e for project-v4-multigroup\r\n        working-directory: testdata/project-v4-multigroup/\r\n        run: |\r\n          make test-e2e\r\n\r\n  # Test to validate e2e integration when no APIs are scaffolded\r\n  e2e-test-basic-project:\r\n    runs-on: ubuntu-latest\r\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\r\n    steps:\r\n      - name: Checkout repository\r\n        uses: actions/checkout@v6.0.2\r\n\r\n      - name: Setup Go\r\n        uses: actions/setup-go@v6\r\n        with:\r\n          go-version-file: go.mod\r\n\r\n      - name: Install the latest version of kind\r\n        run: |\r\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\r\n          chmod +x ./kind\r\n          sudo mv ./kind /usr/local/bin/kind\r\n\r\n      - name: Verify kind installation\r\n        run: kind version\r\n\r\n      - name: Create kind cluster\r\n        run: kind create cluster\r\n\r\n      - name: Build kubebuilder CLI from this repo\r\n        run: make install\r\n\r\n      - name: Scaffold empty go/v4 project\r\n        run: |\r\n          mkdir -p /tmp/basic-project-v4\r\n          cd /tmp/basic-project-v4\r\n          kubebuilder init --plugins go/v4 --domain example.com --repo example.com/empty-operator\r\n          go mod tidy\r\n          make\r\n\r\n      - name: Run make test-e2e on empty project\r\n        working-directory: /tmp/basic-project-v4\r\n        run: make test-e2e\r\n"
  },
  {
    "path": ".github/workflows/test-helm-book.yml",
    "content": "name: Helm Docs Tutorials\n\non:\n  push:\n    paths:\n      - \"docs/book/src/cronjob-tutorial/testdata/project/**\"\n      - \"docs/book/src/getting-started/testdata/project/**\"\n      - \"docs/book/src/multiversion-tutorial/testdata/project/**\"\n      - \".github/workflows/test-helm-book.yml\"\n  pull_request:\n    paths:\n      - \"docs/book/src/cronjob-tutorial/testdata/project/** \"\n      - \"docs/book/src/getting-started/testdata/project/**\"\n      - \"docs/book/src/multiversion-tutorial/testdata/project/**\"\n      - \".github/workflows/test-helm-book.yml\"\n\njobs:\n  helm-test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        folder: [\n          \"docs/book/src/getting-started/testdata/project\",\n          \"docs/book/src/cronjob-tutorial/testdata/project\",\n          \"docs/book/src/multiversion-tutorial/testdata/project\"\n        ]\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Set project name\n        id: project\n        run: echo \"name=$(basename ${{ matrix.folder }})\" >> $GITHUB_OUTPUT\n\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare project\n        run: |\n          cd ${{ matrix.folder }}\n          go mod tidy\n          make docker-build IMG=${{ steps.project.outputs.name}}:v0.1.0\n          kind load docker-image ${{ steps.project.outputs.name}}:v0.1.0\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm chart\n        run: |\n          helm lint ${{ matrix.folder }}/dist/chart\n\n      - name: Install Prometheus Operator CRDs\n        run: |\n          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n          helm repo update\n          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Install cert-manager via Helm\n        run: |\n          helm repo add jetstack https://charts.jetstack.io\n          helm repo update\n          helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true\n\n      - name: Wait for cert-manager to be ready\n        run: |\n          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager\n          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector\n          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook\n\n      - name: Render Helm chart\n        run: |\n          helm template ${{ matrix.folder }}/dist/chart --namespace=${{ steps.project.outputs.name }}-system\n\n      - name: Deploy manager via Helm\n        working-directory: ${{ matrix.folder }}\n        run: |\n          make helm-deploy IMG=${{ steps.project.outputs.name}}:v0.1.0\n\n      - name: Check Helm release status\n        working-directory: ${{ matrix.folder }}\n        run: |\n          make helm-status\n\n      - name: Run Helm tests\n        run: |\n          helm test ${{ steps.project.outputs.name}} --namespace ${{ steps.project.outputs.name}}-system\n\n"
  },
  {
    "path": ".github/workflows/test-helm-samples.yml",
    "content": "name: Helm Testdata Sample\n\non:\n  push:\n    paths:\n      - \"testdata/project-v4-with-plugins/**\"\n      - \".github/workflows/test-helm-samples.yml\"\n  pull_request:\n    paths:\n      - \"testdata/project-v4-with-plugins/**\"\n      - \".github/workflows/test-helm-samples.yml\"\n\njobs:\n  helm-test-project-v4-with-plugins:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Enable Prometheus in kustomize (testdata sample)\n        run: |\n          sed -i 's/^#- \\.\\.\\/prometheus/- ..\\/prometheus/' testdata/project-v4-with-plugins/config/default/kustomization.yaml\n\n      - name: Build kubebuilder CLI\n        run: make build\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Prepare project-v4-with-plugins\n        run: |\n          cd testdata/project-v4-with-plugins/\n          go mod tidy\n          make all\n\n      - name: Rebuild installer and regenerate Helm chart (v2-alpha)\n        working-directory: testdata/project-v4-with-plugins\n        run: |\n          make build-installer\n          ../../bin/kubebuilder edit --plugins=helm/v2-alpha --force\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm chart for project-v4-with-plugins\n        run: |\n          helm lint testdata/project-v4-with-plugins/dist/chart\n\n      - name: Build project-v4-with-plugins\n        run: |\n          cd testdata/project-v4-with-plugins/\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Prometheus Operator CRDs\n        run: |\n          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n          helm repo update\n          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Install cert-manager via Helm (wait for readiness)\n        run: |\n          helm repo add jetstack https://charts.jetstack.io\n          helm repo update\n          helm install cert-manager jetstack/cert-manager \\\n            --namespace cert-manager \\\n            --create-namespace \\\n            --set crds.enabled=true \\\n            --wait \\\n            --timeout 300s\n\n      - name: Render Helm chart for project-v4-with-plugins\n        run: |\n          helm template testdata/project-v4-with-plugins/dist/chart --namespace=project-v4-with-plugins-system\n\n      - name: Deploy manager via Helm\n        working-directory: testdata/project-v4-with-plugins\n        run: |\n          make helm-deploy IMG=controller:latest HELM_EXTRA_ARGS=\"--set prometheus.enable=true\"\n\n      - name: Check Helm release status\n        working-directory: testdata/project-v4-with-plugins\n        run: |\n          make helm-status\n\n      - name: Run Helm tests\n        run: |\n          helm test project-v4-with-plugins --namespace project-v4-with-plugins-system\n\n      - name: Delete kind cluster\n        if: always()\n        run: |\n          kind delete cluster || true\n\n  # Test scenario: \n  # - scaffold project without creating webhooks, \n  # - deploy helm chart without installing cert manager;\n  # - check that deployment has been deployed;\n  #\n  # Command to use to scaffold project without creating webhooks and so no need to install cert manager:\n  # - kubebuilder init\n  # - kubebuilder create api --group example.com --version v1 --kind App --controller=true --resource=true\n  # - kubebuilder edit --plugins=helm.kubebuilder.io/v2-alpha\n  test-helm-no-webhooks:\n    runs-on: ubuntu-latest\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Install kubebuilder binary\n        run: make install\n\n      - name: Create test directory\n        run: mkdir -p test-helm-no-webhooks\n\n      - name: Scaffold project with kubebuilder commands\n        working-directory: test-helm-no-webhooks\n        run: |\n          go mod init test-helm-no-webhooks\n          kubebuilder init\n          kubebuilder create api --group example.com --version v1 --kind App --controller=true --resource=true\n          kubebuilder edit --plugins=helm.kubebuilder.io/v2-alpha\n\n      - name: Build and load Docker image\n        working-directory: test-helm-no-webhooks\n        run: |\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Lint Helm chart\n        working-directory: test-helm-no-webhooks\n        run: helm lint ./dist/chart\n\n      - name: Deploy manager via Helm\n        working-directory: test-helm-no-webhooks\n        run: |\n          make helm-deploy IMG=controller:latest\n\n      - name: Verify deployment is working\n        working-directory: test-helm-no-webhooks\n        run: |\n          make helm-status\n\n      - name: Run Helm tests\n        working-directory: test-helm-no-webhooks\n        run: |\n          helm test test-helm-no-webhooks --namespace test-helm-no-webhooks-system\n\n      - name: Delete kind cluster\n        if: always()\n        run: |\n          kind delete cluster || true\n"
  },
  {
    "path": ".github/workflows/testdata.yml",
    "content": "name: Testdata verification\n\non:\n  push:\n  pull_request:\n\njobs:\n\n  testdata:\n    name: Verify testdata directory\n    runs-on: ubuntu-latest\n    # Pull requests from the same repository won't trigger this checks as they were already triggered by the push\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v6.0.2\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: go.mod\n      - name: Remove pre-installed kustomize\n        # This step is needed as the following one tries to remove\n        # kustomize for each test but has no permission to do so\n        run: sudo rm -f /usr/local/bin/kustomize\n      - name: Verify testdata directory\n        run: make check-testdata\n      - name: Verify docs update\n        run: make check-docs\n"
  },
  {
    "path": ".github/workflows/verify.yml",
    "content": "name: \"PR Title Verifier\"\n\non:\n  pull_request:\n    types: [opened, edited, synchronize, reopened]\n\njobs:\n  verify:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6.0.2\n\n      - name: Validate PR Title Format\n        env:\n          TITLE: ${{ github.event.pull_request.title }}\n        run: |\n          if [[ -z \"$TITLE\" ]]; then\n            echo \"Error: PR title cannot be empty.\"\n            exit 1\n          fi\n\n          if ! [[ \"$TITLE\" =~ ^($'\\u26A0'|$'\\u2728'|$'\\U0001F41B'|$'\\U0001F4D6'|$'\\U0001F680'|$'\\U0001F331') ]]; then\n            echo \"Error: Invalid PR title format.\"\n            echo \"Your PR title must start with one of the following indicators:\"\n            echo \"- Breaking change: ⚠ (U+26A0)\"\n            echo \"- Non-breaking feature: ✨ (U+2728)\"\n            echo \"- Patch fix: 🐛 (U+1F41B)\"\n            echo \"- Docs: 📖 (U+1F4D6)\"\n            echo \"- Release: 🚀 (U+1F680)\"\n            echo \"- Infra/Tests/Other: 🌱 (U+1F331)\"\n            exit 1\n          fi\n\n          echo \"PR title is valid: '$TITLE'\"\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\n.vscode/\nWORKSPACE\n.DS_Store\n# don't check in the build output of the book\ndocs/book/book/\n\n# ignore auto-generated dir by `mdbook serve`\ndocs/book/src/docs\n\n# Editor temp files\n*~\n\\#*#\n*.swp\n\n# skip bin dirs\n**/bin\n**/testbin\n\n# skip GoReleaser dist directory (root level only, not testdata)\n/dist\n\n# skip .out files (coverage tests)\n*.out\n\n# skip testdata go.sum, since it may have\n# different result depending on go version\n/testdata/**/go.sum\n/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1/bin\n/testdata/**legacy**\n\n## Skip testdata files that generate by tests using TestContext\n**/e2e-*/**\n# Optional rendered chart output (e.g. from make yamllint-helm when debugging)\ntestdata/.helm-rendered.yaml"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - asciicheck\n    - bidichk\n    - copyloopvar\n    - depguard\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - importas\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - nolintlint\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - wrapcheck\n    - whitespace\n  settings:\n    depguard:\n      rules:\n        forbid-pkg-errors:\n          deny:\n            - pkg: sort\n              desc: Should be replaced with slices package\n    ginkgolinter:\n      forbid-focus-container: true\n      forbid-spec-pollution: true\n    govet:\n      disable:\n        - fieldalignment\n      enable-all: true\n    importas:\n      no-unaliased: true\n      alias:\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha\n          alias: helmv1alpha\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha\n          alias: helmv2alpha\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha\n          alias: grafanav1alpha\n        - pkg: \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha\"\n          alias: autoupdatev1alpha\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\n          alias: deployimagev1alpha1\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\n          alias: golangv4\n        - pkg: sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\n          alias: kustomizecommonv2\n    modernize:\n      disable:\n      # Suggest replacing omitempty with omitzero for struct fields.\n      # Disable this check for now since it introduces too many changes in our existing codebase.\n      # See https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#hdr-Analyzer_omitzero for more details.\n        - omitzero\n    nolintlint:\n      allow-unused: false\n    revive:\n      rules:\n        - name: blank-imports\n        - name: context-as-argument\n        - name: context-keys-type\n        - name: dot-imports\n          arguments:\n            - allowedPackages:\n                - github.com/onsi/ginkgo/v2\n                - github.com/onsi/gomega\n        - name: error-return\n        - name: error-strings\n        - name: error-naming\n        - name: exported\n          disabled: true\n        - name: if-return\n        - name: import-shadowing\n        - name: increment-decrement\n        - name: var-naming\n          severity: warning\n          arguments:\n            - [\"ID\"] # allowed initialisms\n            - [\"VM\"] # disallowed initialisms\n            - [\n                # <-- this is a list containing one map\n                {\n                  skip-initialism-name-checks: true,\n                  upper-case-const: true,\n                  skip-package-name-checks: true,\n                  extra-bad-package-names: [\"helpers\", \"models\"],\n                },\n              ]\n        - name: var-declaration\n        - name: package-comments\n          disabled: true\n        - name: range\n        - name: receiver-naming\n        - name: time-naming\n        - name: unexported-return\n        - name: indent-error-flow\n        - name: errorf\n        - name: empty-block\n        - name: superfluous-else\n        - name: unused-parameter\n        - name: unreachable-code\n        - name: redefines-builtin-id\n        - name: bool-literal-in-expr\n        - name: constant-logical-expr\n        - name: comment-spacings\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - gosec\n        path: test/e2e/*\n      - linters:\n          - gosec\n          - lll\n        path: hack/docs/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gci\n    - gofmt\n    - gofumpt\n    - goimports\n  settings:\n    gci:\n      sections:\n        - standard\n        - default\n        - prefix(sigs.k8s.io/kubebuilder)\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".yamllint",
    "content": "# yamllint configuration for Kubebuilder\n# Aligns with Kubernetes YAML best practices:\n# - 2-space indentation, spaces only (no tabs)\n# - true/false over yes/no for booleans (truthy)\n# - Newline at end of file, no trailing spaces (POSIX)\n# - Unix newlines, consistent colons/hyphens\n# - No duplicate keys (key-duplicates)\n# - Document start (---) recommended for manifests; not enforced to allow existing testdata\nextends: default\nrules:\n  line-length:\n    max: 120\n    allow-non-breakable-words: true\n    # Generated CRDs and Prometheus manifests often have long lines\n    ignore: |\n      **/config/crd/bases/*.yaml\n      **/config/prometheus/*.yaml\n  indentation:\n    spaces: 2\n    # Kubernetes manifests often use list items at same indent as key (rules:\\n- apiGroups:)\n    indent-sequences: whatever\n    # Uncommented replacement blocks in config/default/kustomization.yaml use 1-space for list items\n    ignore: |\n      **/config/default/kustomization.yaml\n  # Kubernetes: prefer true/false over yes/no; do not check keys (e.g. \"on\" in workflows)\n  truthy:\n    check-keys: false\n  # Allow single trailing blank line (common in editors)\n  empty-lines:\n    max-end: 1\n  # Optional: document-start (---) is K8s best practice but not enforced for testdata\n  document-start: disable\n  document-end: disable\n"
  },
  {
    "path": ".yamllint-helm",
    "content": "# yamllint config for Helm-rendered manifest output (stdin or temp files).\n# Extends repo .yamllint; relaxes rules that are noisy for generated/template output.\nextends: .yamllint\nrules:\n  line-length: disable\n  trailing-spaces: disable\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Kubebuilder AI Agent Guide\n\n**Kubebuilder** is a **framework** and **command-line interface (CLI)** for building **Kubernetes APIs** using **Custom Resource Definitions (CRDs)**.\nIt provides scaffolding and abstractions that accelerate the development of **controllers**, **webhooks**, and **APIs** written in **Go**.\n\n## Quick Reference\n\n| Item       | Value                                                     |\n|------------|-----------------------------------------------------------|\n| Language   | Defined in the go.mod                                     |\n| Module     | `sigs.k8s.io/kubebuilder/v4`                              |\n| Binary     | `./bin/kubebuilder`                                       |\n| Core deps  | `controller-runtime`, `controller-tools`, Helm, Kustomize |\n| Docs       | https://book.kubebuilder.io                               |\n\n\n## Directory Map\n\n```\npkg/\n  cli/              CLI command implementations\n    alpha/          Alpha/experimental commands (generate, update, etc.)\n    init.go         'init' command + default PluginBundle definition\n    api.go          'create api' command\n    webhook.go      'create webhook' command\n    edit.go         'edit' command\n    root.go         Root command setup\n  machinery/        Scaffolding engine (templates, markers, injectors)\n    template.go     Base template interface\n    inserter.go     Code injection engine\n    marker.go       Marker detection and processing\n    filesystem.go   Filesystem abstraction (uses afero)\n  model/\n    resource/       Resource model (GVK, API, Controller, Webhook)\n    stage/          Plugin execution stages\n  plugin/           Plugin interfaces and utilities\n    interface.go    Core plugin interfaces (Plugin, Init, CreateAPI, etc.)\n    bundle.go       Plugin composition\n    util/           Helper functions for plugin authors\n  plugins/          Plugin implementations (ADD NEW PLUGINS HERE)\n    golang/v4/      Main Go scaffolding (default for go projects)\n      scaffolds/    Scaffolding for init, api, webhook\n        internal/templates/  Template implementations\n    golang/deployimage/  Deploy-image pattern plugin\n    common/kustomize/v2/  Kustomize manifest generation (default)\n    optional/       Optional plugins (enabled via --plugins flag)\n      helm/         Helm chart generation (v1alpha deprecated, v2alpha current)\n      grafana/      Grafana dashboard generation\n      autoupdate/   Auto-update GitHub workflow\n    external/       External plugin support (exec-based plugins)\ndocs/book/          mdBook documentation (https://book.kubebuilder.io)\n  src/              Markdown source files\n    **/testdata/    Sample projects used in docs (regenerated)\ntest/\n  e2e/              E2E tests requiring Kubernetes cluster\n    v4/             Tests for v4 plugin\n    helm/           Tests for Helm plugin\n    deployimage/    Tests for deploy-image plugin\n    utils/          Test helpers (TestContext, etc.)\n  testdata/         Scripts to generate testdata projects\n    generate.sh     Main generation script\n    test.sh         Tests all testdata projects\ntestdata/           Generated complete sample projects (DO NOT EDIT)\n  project-v4/                    Basic v4 project\n  project-v4-multigroup/         Multigroup project\n  project-v4-with-plugins/       Project with optional plugins\nhack/docs/          Documentation generation\n  generate.sh       Regenerate docs samples + marker docs\n  generate_samples.go  Sample generation logic\ncmd/                CLI entry point\n  version.go        Version info (updated by make update-k8s-version)\nmain.go             Application entry point\n```\n\n**Key Locations for Common Tasks:**\n- Add new plugin → `pkg/plugins/<category>/<name>/`\n- Add new template → `pkg/plugins/<plugin>/scaffolds/internal/templates/`\n- Modify CLI commands → `pkg/cli/`\n- Add scaffolding machinery → `pkg/machinery/`\n- Add tests → `test/e2e/all/plugin_<name>_test.go` or `pkg/<package>/*_test.go`\n\n## Critical Rules\n\n### Do Not Manually Edit Generated Files\n- `testdata/` - regenerated via `make generate-testdata`\n- `docs/book/**/testdata/` - regenerated via `make generate-docs`\n- `*/dist/chart/` - regenerated via `make generate-charts`\n\n### File-Specific Requirements\n\nAfter making changes, run the appropriate commands based on what you modified:\n\n**Generate Commands (rebuild artifacts):**\n- **If you modify files in `hack/docs/internal/`** → run `make install && make generate-docs`\n- **If you modify files in `pkg/plugins/optional/helm/`** → run `make install && make generate-charts`\n- **If you modify any boilerplate/template files** → run `make install && make generate`\n\n**Formatting Commands:**\n- After editing `*.go` → `make lint-fix`\n- After editing `*.md` → `make remove-spaces`\n\n**Always Run Before PR:**\n```bash\nmake lint-fix    # Auto-fix Go code style\nmake test-unit   # Verify unit tests pass\n```\n\n**Note:** Boilerplate/template files are Go files that define scaffolding templates, typically located in `pkg/plugins/**/scaffolds/internal/templates/` or files that generate code/configs for scaffolded projects.\n\n## Development Workflow\n\n### Build & Install\n```bash\nmake build    # Build to ./bin/kubebuilder\nmake install  # Copy to $(go env GOBIN)\n```\n\n### Lint & Format\n```bash\nmake lint       # Check only (golangci-lint + yamllint)\nmake lint-fix   # Auto-fix Go code\n```\n\n### Testing\n```bash\nmake test-unit         # Fast unit tests (./pkg/..., ./test/e2e/utils/...)\nmake test-integration  # Integration tests (may create temp dirs, download binaries)\nmake test-testdata     # Test all testdata projects\nmake test-e2e-local    # Full e2e (creates kind cluster)\nmake test              # CI aggregate (all of above + license)\n```\n\n## PR Submission\n\n### PR Title Format (MANDATORY)\n\nPR titles use **emojis** (appear in release notes).\n\nFormat: `:emoji: [(plugin/version)]: Description`\n\nThe `(plugin/version)` scope is optional; omit it for repo-wide or documentation-only changes.\n\n**Emojis:**\n- ⚠️ (`:warning:`) - Breaking change\n- ✨ (`:sparkles:`) - New feature\n- 🐛 (`:bug:`) - Bug fix\n- 📖 (`:book:`) - Documentation\n- 🌱 (`:seedling:`) - Infrastructure/tests/refactor\n\n**Examples:**\n```\n🐛 Resolve nil pointer panic in scaffold generator\n✨ (helm/v2-alpha): Add cluster-scoped resource support\n📖 (go/v4): Update deployment documentation\n✨ Update dependencies to latest versions\n```\n\n### Commit Message Format\n\nCommit messages follow the [Conventional Commits](https://www.conventionalcommits.org/) standard.\n\nFormat: `<type>[optional scope]: <description>`\n\nThe `[optional scope]` is typically the plugin/version (e.g., `helm/v2-alpha`, `go/v4`); omit it for repo-wide or non-plugin changes.\n\n**Types:**\n\n- **feat**: A new feature for the user or a plugin\n- **fix**: A bug fix for the user or a plugin\n- **docs**: Documentation changes only\n- **test**: Adding or updating tests\n- **refactor**: Code change that neither fixes a bug nor adds a feature\n- **chore**: Changes to build process, dependencies, or maintenance tasks\n- **breaking**: A breaking change (can be combined with other types)\n\n**Examples:**\n```\nfix: Resolve nil pointer panic in scaffold generator\nfeat(helm/v2-alpha): Add cluster-scoped resource support\ndocs(go/v4): Update deployment documentation\nchore: Update dependencies to latest versions\n```\n\n### Pre-PR Checklist\n- [ ] One commit per PR (squash all)\n- [ ] Add/update tests for new behavior\n- [ ] Add/update docs for new behavior\n- [ ] Run `make lint-fix`\n- [ ] Run `make install`\n- [ ] Run `make generate`\n- [ ] Run `make test-unit`\n\n## Core Concepts\n\n### Plugin Architecture\n\nPlugins implement interfaces from `pkg/plugin/`:\n- `Plugin` - base interface (Name, Version, SupportedProjectVersions)\n- `Init` - project initialization (`kubebuilder init`)\n- `CreateAPI` - API creation (`kubebuilder create api`)\n- `CreateWebhook` - webhook creation (`kubebuilder create webhook`)\n- `Edit` - post-init modifications (`kubebuilder edit`)\n- `Bundle` - groups multiple plugins\n\n**Plugin Bundles:**\n\nDefault bundle (`pkg/cli/init.go`): `go.kubebuilder.io/v4` + `kustomize.common.kubebuilder.io/v2`\n\nPlugins resolve via `pkg/plugin` registry and execute in order.\n\n**External Plugins:**\n\nExecutable binaries in `pkg/plugins/external/` that communicate via JSON over stdin/stdout.\n\n### Scaffolding Machinery\n\nFrom `pkg/machinery/`:\n- `Template` - file generation via Go templates\n- `Inserter` - code injection at markers\n- `Marker` - special comments (e.g., `// +kubebuilder:scaffold:imports`)\n- `Filesystem` - abstraction over afero for testability\n\n### Scaffolded Project Structure\n\nProjects generated by the Kubebuilder CLI use the default plugin bundle (`go/v4` + `kustomize/v2`). Each plugin scaffolds different files:\n\n**`go/v4` plugin scaffolds Go code:**\n- `cmd/main.go` - Entry point (manager setup)\n- `api/v1/*_types.go` - API definitions with `+kubebuilder` markers (via `create api`)\n- `internal/controller/*_controller.go` - Reconcile logic (via `create api`)\n- `Dockerfile`, `Makefile` - Build and deployment automation\n\n**`kustomize/v2` plugin scaffolds manifests:**\n- `config/` - Kustomize base manifests (CRDs, RBAC, manager, webhooks)\n- `config/crd/` - Custom Resource Definitions (via `create api`)\n- `config/samples/` - Example CR manifests (via `create api`)\n\n**`PROJECT` file:**\n- Project configuration tracking plugins, resources, domain, and layout\n\n**Note:** These are files in projects generated BY Kubebuilder, not the Kubebuilder source code itself.\n\n### Reconciliation Pattern\n\nControllers implement `Reconcile(ctx, req) (ctrl.Result, error)`:\n\n- **Idempotent** - Safe to run multiple times\n- **Level-triggered** - React to current state, not events\n- **Requeue on pending work** - Return `ctrl.Result{Requeue: true}`\n\n### Testing Pattern\nE2E tests use `utils.TestContext` from `test/e2e/utils/test_context.go`:\n\n```go\nctx := utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\nctx.Init(\"--domain\", \"example.com\", \"--repo\", \"example.com/project\")\nctx.CreateAPI(\"--group\", \"crew\", \"--version\", \"v1\", \"--kind\", \"Captain\")\nctx.Make(\"build\", \"test\")\nctx.LoadImageToKindCluster()\n```\n\n## CLI Reference\n\nAfter `make install`:\n\n```bash\nkubebuilder init --domain example.com --repo github.com/example/myproject\nkubebuilder create api --group batch --version v1 --kind CronJob\nkubebuilder create webhook --group batch --version v1 --kind CronJob\nkubebuilder edit --plugins=helm/v2-alpha\nkubebuilder alpha generate    # Experimental: generate from PROJECT file\nkubebuilder alpha update      # Experimental: update to latest plugin versions\n```\n\n## Common Patterns\n\n### Code Style\n- Avoid abbreviations: `context` not `ctx` (except receivers)\n- Descriptive names: `projectConfig` not `pc`\n- Single/double-letter receivers OK: `(c CLI)` or `(p Plugin)`\n\n### Logging Conventions\n\nKubebuilder has two distinct types of code with different logging conventions:\n\n**1. Kubebuilder CLI Tool Code** → Go CLI best practices\n\nApplies to: `pkg/cli/*`, `pkg/plugins/*`, `pkg/machinery/*`, `pkg/config/*`, `pkg/model/*`, etc.\n\nThis is the Kubebuilder tool itself. Follow Go logging conventions for CLI tools:\n- **First word lowercase**, sentences after periods capitalized: `\"unable to find file. This file is required for...\"`\n- **No ending punctuation** (but use periods between sentences)\n- **Error strings lowercase**: `fmt.Errorf(\"something bad\")`\n\n```go\nlog.Info(\"writing scaffold for you to edit\")\nlog.Warn(\"unable to find boilerplate file. This file is used to generate the license header\")\nlog.Error(\"failed to read file\", \"file\", path)\nreturn fmt.Errorf(\"failed to load config: %w\", err)\n```\n\n**2. Generated Code (Template Output)** → Kubernetes conventions\n\nApplies to: Code GENERATED by templates in `pkg/plugins/*/scaffolds/internal/templates/*`\n\nTemplates produce controller code that runs in Kubernetes clusters. The GENERATED code follows Kubernetes conventions:\n- **Start with capital letter**: `\"Starting reconciliation\"`\n- **No ending period** (but use periods between sentences)\n- **Past tense**: `\"Failed to create Pod\"` not `\"Cannot create Pod\"`\n- **Active voice**: specify subject or omit when it's the program itself\n- **Specify object type**: `\"Created Deployment\"` not `\"Created\"`\n\n```go\n// In template files that generate controller code:\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Note:** The distinction is based on WHERE the code runs:\n- CLI tool (runs on developer's machine) → Go conventions\n- Generated controllers (run in Kubernetes cluster) → Kubernetes conventions\n\n### Testing Philosophy\n- Test behaviors, not implementations\n- Use real components over mocks\n- Test cases as specifications (Ginkgo: `Describe`, `It`, `Context`, `By`)\n- Use **Ginkgo v2** + **Gomega** for BDD-style tests.\n- Tests depending on the Kubebuilder binary should use: `utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")`\n\n### Test Organization\n\n- **Unit tests** (`*_test.go` in `pkg/`) - Test individual packages in isolation, fast\n- **Integration tests** (`*_integration_test.go` in `pkg/`) - Test multiple components together without cluster\n  - Must have `//go:build integration` tag at the top\n  - May create temp dirs, download binaries, or scaffold files\n  - Examples: alpha update, grafana scaffolding, helm chart generation\n- **E2E tests** (`test/e2e/`) - **ONLY** for tests requiring a Kubernetes cluster (KIND)\n  - `v4/plugin_cluster_test.go` - Test v4 plugin deployment\n  - `helm/plugin_cluster_test.go` - Test Helm chart deployment\n  - `deployimage/plugin_cluster_test.go` - Test deploy-image plugin\n\n### Scaffolding\n- Use library helpers from `pkg/plugin/util/`\n- Use markers for extensibility\n- Follow existing template patterns in `pkg/machinery`\n\n## Search Tips\n\n```bash\nrg \"\\\\+kubebuilder:scaffold\" --type go  # Find markers\nrg \"type.*Plugin struct\" pkg/plugins/   # Plugin implementations\nrg \"PluginBundle\" pkg/cli/              # Plugin registration\nrg \"func.*SetTemplateDefaults\"          # Template definitions\nrg \"func new.*Command\" pkg/cli/         # CLI commands\nrg \"NewTestContext\" test/e2e/           # E2E test setup\n```\n\n## Design Philosophy\n\n- **Libraries over code generation** - Use libraries when possible; generated code is hard to maintain\n- **Common cases easy, uncommon cases possible** - 80-90% use cases should be simple\n- **Batteries included** - Projects should be deployable/testable out-of-box\n- **No copy-paste** - Refactor into libraries or remote Kustomize bases\n\n## References\n\n### Essential Files\n- **`Makefile`** - All automation targets (source of truth for build/test commands)\n- **`CONTRIBUTING.md`** - CLA, pre-submit checklist, PR requirements\n- **`VERSIONING.md`** - Release workflow, versioning policy, PR tagging\n- **`go.mod`** - Go version and dependencies\n\n### Key Directories\n- **`pkg/`** - Core Kubebuilder code (CLI, plugins, machinery)\n- **`test/e2e/`** - End-to-end tests with Kubernetes cluster\n- **`testdata/`** - Generated sample projects (regenerated automatically)\n- **`docs/book/`** - User documentation source (https://book.kubebuilder.io)\n\n### Important Code Files\n- **`pkg/cli/init.go`** - Default plugin bundle definition\n- **`pkg/plugin/interface.go`** - Plugin interface definitions\n- **`pkg/machinery/scaffold.go`** - Scaffolding engine\n- **`test/e2e/utils/test_context.go`** - E2E test helpers\n- **`cmd/version.go`** - Version info (includes K8S version)\n\n### Scripts\n- **`test/testdata/generate.sh`** - Regenerate all testdata projects\n- **`hack/docs/generate.sh`** - Regenerate documentation samples\n- **`test/e2e/local.sh`** - Run e2e tests locally with Kind\n\n### External Resources\n- **Kubebuilder Book**: https://book.kubebuilder.io\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Kubernetes Logging Conventions:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n- **Structured Logging Guidelines:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nThis document describes how to contribute to the project.\n\n## Sign the CLA\n\nKubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests.\n\nPlease see https://git.k8s.io/community/CLA.md for more info.\n\n## Prerequisites\n\n- [go](https://golang.org/dl/) version v1.23+.\n- [docker](https://docs.docker.com/install/) version 17.03+.\n- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) version v1.11.3+.\n- [kustomize](https://github.com/kubernetes-sigs/kustomize/blob/master/site/content/en/docs/Getting%20started/installation.md) v3.1.0+\n- Access to a Kubernetes v1.11.3+ cluster.\n\n## Contributing steps\n\n1. Submit an issue describing your proposed change to the repo in question.\n1. The [repo owners](OWNERS) will respond to your issue promptly.\n1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).\n1. Fork the desired repo, develop and test your code changes.\n1. Submit a pull request.\n\nIn addition to the above steps, we adhere to the following best practices to maintain consistency and efficiency in our project:\n\n- **Single Commit per PR:** Each Pull Request (PR) should contain only one commit. This approach simplifies tracking changes and makes the history more readable.\n- **One Issue per PR:** Each PR should address a single specific issue or need. This helps in streamlining our workflow and makes it easier to identify and resolve problems such as revert the changes if required.\n\nFor more detailed guidelines, refer to the [Kubernetes Contributor Guide][k8s-contrubutiong-guide].\n\n## How to build kubebuilder locally\n\nNote that, by building the kubebuilder from the source code we are allowed to test the changes made locally.\n\n1. Run the following command to clone your fork of the project locally in the dir /src/sigs.k8s.io/kubebuilder\n\n```\n$ git clone git@github.com:<user>/kubebuilder.git $GOPATH/src/sigs.k8s.io/kubebuilder\n```\n\n1. Ensure you activate module support before continue (`$ export GO111MODULE=on`)\n1. Run the command `make install` to create a bin with the source code\n\n**NOTE** In order to check the local environment run `make test-unit`.\n\n## What to do before submitting a pull request\n\n1. Run the script `make generate` to update/generate the mock data used in the e2e test in `$GOPATH/src/sigs.k8s.io/kubebuilder/testdata/`\n1. Run `make test-unit test-e2e-local`\n\n- e2e tests use [`kind`][kind] and [`setup-envtest`][setup-envtest]. If you want to bring your own binaries, place them in `$(go env GOPATH)/bin`.\n\n**IMPORTANT:** The `make generate` is very helpful. By using it, you can check if good part of the commands still working successfully after the changes. Also, note that its usage is a prerequisite to submit a PR.\n\nFollowing the targets that can be used to test your changes locally.\n\n| Command             | Description                                                   | Is called in the CI? |\n| ------------------- | ------------------------------------------------------------- | -------------------- |\n| make test-unit      | Runs go tests                                                 | no                   |\n| make test           | Runs tests in shell (`./test.sh`)                             | yes                  |\n| make lint           | Run [golangci][golangci] lint checks                          | yes                  |\n| make lint-fix       | Run [golangci][golangci] to automatically perform fixes       | no                   |\n| make test-coverage  | Run coveralls to check the % of code covered by tests         | yes                  |\n| make check-testdata | Checks if the testdata dir is updated with the latest changes | yes                  |\n| make test-e2e-local | Runs the CI e2e tests locally                                 | no                   |\n\n**NOTE** `make lint` requires a local installation of `golangci-lint`. More info: https://github.com/golangci/golangci-lint#install\n\n### Running e2e tests locally\n\nSee that you can run `test-e2e-local` to setup Kind and run e2e tests locally.\nAnother option is by manually starting up Kind and configuring it and then,\nyou can for example via your IDEA debug the e2e tests.\n\nTo manually setup run:\n\n```shell\n# To generate an Kubebuilder local binary with your changes\nmake install\n# To create the cluster\nkind create cluster --config ./test/e2e/kind-config.yaml\n```\n\nNow, you can for example, run in debug mode the `test/e2e/all/e2e_suite_test.go`:\n\n![example](https://github.com/kubernetes-sigs/kubebuilder/assets/7708031/277d26d5-c94d-41f0-8f02-1381458ef750)\n\n### Test Plugin\n\nIf your intended PR creates a new plugin, make sure the PR also provides test cases. Testing should include:\n\n1. `e2e tests` to validate the behavior of the proposed plugin.\n2. `sample projects` to verify the scaffolded output from the plugin.\n\n#### 1. Plugin E2E Tests\n\nAll the plugins provided by Kubebuilder should be validated through `e2e-tests` across multiple platforms.\n\nCurrent Kubebuilder provides the testing framework that includes testing code based on [ginkgo](https://github.com/onsi/ginkgo), [Github Actions](https://github.com/Kavinjsir/kubebuilder/blob/docs%2Ftest-plugin/.github/workflows/testdata.yml) for unit tests, and multiple env tests driven by [test-infra](https://github.com/kubernetes/test-infra/blob/master/config/jobs/kubernetes-sigs/kubebuilder/kubebuilder-presubmits.yaml).\n\nTo fully test the proposed plugin:\n\n1. Add test specs to `test/e2e/plugin_<your-plugin>_test.go` in the unified test suite.\n2. Tests should use the shared `e2e_suite_test.go` BeforeSuite/AfterSuite hooks (cert-manager and Prometheus are already installed).\n3. Each test should:\n   - Initialize a `TestContext` using `utils.NewTestContext`\n   - Trigger the plugin's bound subcommands. See [Init](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L213), [CreateAPI](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.6.0/test/e2e/utils/test_context.go#L222)\n   - Use [PluginUtil](https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util) to verify the scaffolded outputs\n4. Test validation should:\n\n   - 4.1. Setup testing environment, e.g:\n\n     - Cleanup environment, create temp dir. See [Prepare](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L97)\n     - If your test will cover the provided features then, ensure that you install prerequisites CRDs: See [InstallCertManager](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L138), [InstallPrometheusManager](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.6.0/test/e2e/utils/test_context.go#L171)\n\n   - 4.2. Run the function from `generate_test.go`.\n\n   - 4.3. Further make sure the scaffolded output works, e.g:\n\n     - Execute commands in your `Makefile`. See [Make](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L240)\n     - Temporary load image of the testing controller. See [LoadImageToKindCluster](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L283)\n     - Call Kubectl to validate running resources. See [utils.Kubectl](https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#Kubectl)\n\n   - 4.4. Delete temporary resources after testing exited, e.g:\n     - Uninstall prerequisites CRDs: See [UninstallPrometheusOperManager](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L183)\n     - Delete temp dir. See [Destroy](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/utils/test_context.go#L255)\n\n5. Add the command in [test/e2e/plugin](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/e2e/setup.sh#L65) to run your testing code:\n\n```shell\ngo test $(dirname \"$0\")/<your-plugin-test-folder> $flags -timeout 30m\n```\n\n#### 2. Sample Projects from the Plugin\n\nIt is also necessary to test consistency of the proposed plugin across different env and the integration with other plugins.\n\nThis is performed by generating sample projects based on the plugins. The CI workflow defined in Github Action would validate the availability and the consistency.\n\nSee:\n\n- [test/testdata/generated.sh](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/test/testdata/generate.sh#L144)\n- [make generate](https://github.com/kubernetes-sigs/kubebuilder/blob/v3.7.0/Makefile#L70)\n\n## PR Process\n\nSee [VERSIONING.md](VERSIONING.md) for a full description. TL;DR:\n\n### PR Title Format\n\nPR titles use **emojis** (appear in release notes). Format: `:emoji: (plugin/version): Description`\n\n**Emojis:**\n- ⚠️ (`:warning:`) - Breaking change\n- ✨ (`:sparkles:`) - New feature\n- 🐛 (`:bug:`) - Bug fix\n- 📖 (`:book:`) - Documentation\n- 🌱 (`:seedling:`) - Infrastructure/tests/refactor\n- 👻 (`:ghost:`) - No release note (unreleased changes only)\n\n**Examples:**\n```\n🐛 Resolve nil pointer panic in scaffold generator\n✨ (helm/v2-alpha): Add cluster-scoped resource support\n📖 (go/v4): Update deployment documentation\n✨ Update dependencies to latest versions\n🌱 Add new GitHub action to test out doc samples\n```\n\n### Commit Message Format\n\nCommit messages follow the [Conventional Commits](https://www.conventionalcommits.org/) standard.\n\nFormat: `<type>[optional scope]: <description>`\n\nThe `[optional scope]` is typically the plugin/version (e.g., `helm/v2-alpha`, `go/v4`); omit it for repo-wide or non-plugin changes.\n\n**Types:**\n\n- **feat**: A new feature for the user or a plugin\n- **fix**: A bug fix for the user or a plugin\n- **docs**: Documentation changes only\n- **test**: Adding or updating tests\n- **refactor**: Code change that neither fixes a bug nor adds a feature\n- **chore**: Changes to build process, dependencies, or maintenance tasks\n- **breaking**: A breaking change (can be combined with other types)\n\n**Examples:**\n```\nfix: Resolve nil pointer panic in scaffold generator\nfeat(helm/v2-alpha): Add cluster-scoped resource support\ndocs(go/v4): Update deployment documentation\nchore: Update dependencies to latest versions\n```\n\n## Where the CI Tests are configured\n\n1. See the [action files](.github/workflows) to check its tests, and the scripts used on it.\n2. Note that the prow tests used in the CI are configured in [kubernetes-sigs/kubebuilder/kubebuilder-presubmits.yaml](https://github.com/kubernetes/test-infra/blob/master/config/jobs/kubernetes-sigs/kubebuilder/kubebuilder-presubmits.yaml).\n3. Check that all scripts used by the CI are defined in the project.\n4. Notice that our policy to test the project is to run against k8s version N-2. So that the old version should be removed when there is a new k8s version available.\n\n## How to contribute to docs\n\nThe docs are published off of three branches:\n\n- `book-v4`: [book.kubebuilder.io](https://book.kubebuilder.io) -- current docs\n- `book-v3`: [book-v3.book.kubebuilder.io](https://book-v3.book.kubebuilder.io) -- legacy docs\n- `book-v2`: [book-v2.book.kubebuilder.io](https://book-v2.book.kubebuilder.io) -- legacy docs\n- `book-v1`: [book-v1.book.kubebuilder.io](https://book-v1.book.kubebuilder.io) -- legacy docs\n- `master`: [master.book.kubebuilder.io](https://master.book.kubebuilder.io) -- \"nightly\" docs\n\nSee [VERSIONING.md](VERSIONING.md#book-releases) for more information.\n\nThe documentation is rendered using [mdBook with its advanced Markdown features](https://rust-lang.github.io/mdBook/format/markdown.html).\n\nThere are certain writing style guidelines for Kubernetes documentation, checkout [style guide](https://kubernetes.io/docs/contribute/style/style-guide/) for more information.\n\n### How to preview the changes performed in the docs\n\nCheck the CI job after to do the Pull Request and then, click on in the `Details` of `netlify/kubebuilder/deploy-preview`\n\n## Community, discussion and support\n\nLearn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).\n\nYou can reach the maintainers of this project at:\n\n- [Slack](http://slack.k8s.io/)\n- [Mailing List](https://groups.google.com/forum/#!forum/kubebuilder)\n\n## Becoming a reviewer or approver\n\nContributors may eventually become official reviewers or approvers in\nKubebuilder and the related repositories. See\n[CONTRIBUTING-ROLES.md](docs/CONTRIBUTING-ROLES.md) for more information.\n\n## Code of conduct\n\nParticipation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).\n\n[golangci]: https://github.com/golangci/golangci-lint\n[kind]: https://kind.sigs.k8s.io/#installation-and-usage\n[setup-envtest]: https://book.kubebuilder.io/reference/envtest\n[k8s-contrubutiong-guide]: https://www.kubernetes.dev/docs/guide/contributing/\n"
  },
  {
    "path": "DESIGN.md",
    "content": "# Kubebuilder Design Principles\n\nThis lays out some of the guiding design principles behind the Kubebuilder\nproject and its various components.\n\n## Overarching\n\n* **Libraries Over Code Generation**: Generated code is messy to maintain,\n  hard for humans to change and understand, and hard to update.  Library\n  code is easy to update (just increase your dependency version), easier\n  to version using existing mechanisms, and more concise.\n\n* **Copy-pasting is bad**: Copy-pasted code suffers from similar problems\n  as code generation, except more acutely.  Copy-pasted code is nearly\n  impossible to easy update, and frequently suffers from bugs and\n  misunderstandings.  If something is being copy-pasted, it should\n  refactored into a library component or remote\n  [kustomize](https://sigs.k8s.io/kustomize) base.\n\n* **Common Cases Should Be Easy**: The 80-90% common cases should be\n  simple and easy for users to understand.\n\n* **Uncommon Cases Should Be Possible**: There shouldn't be situations\n  where it's downright impossible to do something within\n  controller-runtime or controller-tools. It may take extra digging or\n  coding, and it may involve interoperating with lower-level components,\n  but it should be possible without unreasonable friction.\n\n## Kubebuilder\n\n* **Kubebuilder Has Opinions**: Kubebuilder exists as an opinionated\n  project generator.  It should strive to give users a reasonable project\n  layout that's simple enough to understand when getting started, but\n  provides room to grow.  It might not match everyone's opinions, but it\n  should strive to be useful to most.\n\n* **Batteries Included**: Kubebuilder projects should contain enough\n  deployment information to reasonably develop and run the scaffolded\n  project.  This includes testing, deployment files, and development\n  infrastructure to go from code to running containers.\n\n## controller-tools and controller-runtime\n\n* **Sufficient But Composable**: controller-tools and controller-runtime\n  should be sufficient for building a custom controller by hand.  While\n  scaffolding and additional libraries may make life easier, building\n  without should be as painless as possible.  That being said, they should\n  strive to be usable as building blocks for higher-level libraries as\n  well.\n\n* **Self-Sufficient Docs**: controller-tools and controller-runtime should\n  strive to have self-sufficient docs (i.e. documentation that doesn't\n  require reading other libraries' documentation for common use cases).\n  Examples should be plentiful.\n\n* **Contained Arcana**: Developers should not need to be experts in\n  Kubernetes API machinery to develop controllers, but those familiar with\n  Kubernetes API machinery should not feel out of place.  Abstractions\n  should be intuitive to new users but feel familiar to experienced ones.\n  Abstractions should embrace the concepts of Kubernetes (e.g. declarative\n  idempotent reconcilers) while simplifying the details.\n\n## controller-runtime\n\n* **Abstractions Should Be Layered**: Abstractions should be built on top\n  of lower layers, such that advanced users can write custom logic while\n  still working within the existing model.  For instance, the controller\n  builder is built on top of the event, source, and handler helpers, which\n  are in turn built for use with the event, source, and handler\n  interfaces.\n\n* **Repetitive Stress Injuries Are Bad**:\n  When possible, commonly used pieces should be exposed in a way that\n  enables clear, concise code.  This includes aliasing groups of\n  functionality under \"alias\" or \"prelude\" packages to avoid having 40\n  lines of imports, including common idioms as flexible helpers, and\n  infering resource information from the user's object types in client\n  code.\n\n* **A Little Bit of Magic Goes a Long Way**: In absence of generics,\n  reflection is acceptable, especially when it leads to clearer, conciser\n  code.  However, when possible interfaces that use reflection should be\n  designed to avoid requiring the end-developer to use type assertions,\n  string splitting, which are error-prone and repetitive.  These should be\n  dealt with inside controller-runtime internals.\n\n* **Defaults Over Constructors**: When not a huge performance impact,\n  favor auto-defaulting and `Options` structs over constructors.\n  Constructors quickly become unclear due to lack of names associated\n  with values, and don't work well with optional values.\n\n## Development\n\n* **Words Are Better Than Letters**: Don't abbreviate variable names\n  unless it's blindingly obvious what they are (e.g. `ctx` for `Context`).\n  Single- and double-letter method receivers are acceptable, but single-\n  and double-letter variables quickly become confusing the longer a code\n  block gets.\n\n* **Well-commented code**: Code should be commented and given Godocs, even\n  private methods and functions. It may *seem* obvious what they do at the\n  time and why, but you might forget, and others will certainly come along.\n\n* **Test Behaviors**: Test cases should be comprehensible as sets of\n  expected behaviors.  Test cases read without code (e.g. just using `It`,\n  `Describe`, `Context`, and `By` lines) should still be able to explain\n  what's required of the tested interface. Testing behaviors makes\n  internal refactors easier, and makes reading tests easier.\n\n* **Real Components Over Mocks**: Avoid mocks and recording actions. Mocks\n  tend to be brittle and gradually become more complicated over time (e.g.\n  fake client implementations tend to grow into poorly-written, incomplete\n  API servers).  Recording of actions tends to lead to brittle tests that\n  requires changes during refactors.  Instead, test that the end desired\n  state is correct.  Test the way the world should be, without caring how\n  it got there, and provide easy ways to set up the real components so\n  that mocks aren't required.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "Makefile",
    "content": "#!/usr/bin/env bash\n\n#  Copyright 2023 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\n#\n# Makefile with some common workflow for dev, build and test\n#\nexport GOPROXY?=https://proxy.golang.org/\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p $(LOCALBIN)\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Build\n\n.PHONY: build\nbuild: ## Build the project locally\n\tgo build --trimpath -o bin/kubebuilder\n\n.PHONY: install\ninstall: build ## Build and install the binary with the current source code. Use it to test your changes locally.\n\trm -f $(GOBIN)/kubebuilder\n\tcp ./bin/kubebuilder $(GOBIN)/kubebuilder\n\n##@ Development\n\n.PHONY: generate\ngenerate: generate-testdata generate-docs ## Update/generate all mock data. You should run this commands to update the mock data after your changes.\n\tgo mod tidy\n\tmake remove-spaces\n\n.PHONY: remove-spaces\nremove-spaces:\n\t@echo \"Removing trailing spaces\"\n\t@bash -c ' \\\n\t\tif sed --version 2>&1 | grep -q \"GNU\"; then \\\n\t\t\tfind . -type f -name \"*.md\" -exec sed -i \"s/[[:space:]]*$$//\" {} + || true; \\\n\t\telse \\\n\t\t\tfind . -type f -name \"*.md\" -exec sed -i \"\" \"s/[[:space:]]*$$//\" {} + || true; \\\n\t\tfi'\n\n.PHONY: generate-testdata\ngenerate-testdata: ## Update/generate the testdata in $GOPATH/src/sigs.k8s.io/kubebuilder\n\tchmod -R +w testdata/\n\trm -rf testdata/\n\t./test/testdata/generate.sh\n\n.PHONY: generate-docs\ngenerate-docs: ## Update/generate the docs\n\t./hack/docs/generate.sh\n\n.PHONY: generate-charts\ngenerate-charts: build ## Re-generate the helm chart testdata and docs samples\n\trm -rf testdata/project-v4-with-plugins/dist/chart\n\trm -rf docs/book/src/getting-started/testdata/project/dist/chart\n\trm -rf docs/book/src/cronjob-tutorial/testdata/project/dist/chart\n\trm -rf docs/book/src/multiversion-tutorial/testdata/project/dist/chart\n\n\t# Generate helm charts from kustomize manifests using v2-alpha plugin\n\t(cd testdata/project-v4-with-plugins && make build-installer && ../../bin/kubebuilder edit --plugins=helm/v2-alpha)\n\t(cd docs/book/src/getting-started/testdata/project && make build-installer && ../../../../../../bin/kubebuilder edit --plugins=helm/v2-alpha)\n\t(cd docs/book/src/cronjob-tutorial/testdata/project && make build-installer && ../../../../../../bin/kubebuilder edit --plugins=helm/v2-alpha)\n\t(cd docs/book/src/multiversion-tutorial/testdata/project && make build-installer && ../../../../../../bin/kubebuilder edit --plugins=helm/v2-alpha)\n\n.PHONY: check-docs\ncheck-docs: ## Run the script to ensure that the docs are updated\n\t./hack/docs/check.sh\n\n.PHONY: lint\nlint: golangci-lint yamllint check-sample-permissions ## Run golangci-lint linter, yamllint & sample permissions check\n\t$(GOLANGCI_LINT) run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t$(GOLANGCI_LINT) run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t$(GOLANGCI_LINT) config verify\n\n# Lint all YAML: testdata files (yamllint-yaml) + Helm-rendered charts (yamllint-helm).\n# Repo YAML uses .yamllint; Helm output uses .yamllint-helm.\nYAMLLINT_FILES := $(shell find testdata -name '*.yaml' ! -path 'testdata/.helm-rendered.yaml' \\( ! -path 'testdata/*/dist/*' -o -path 'testdata/*/dist/chart/Chart.yaml' -o -path 'testdata/*/dist/chart/values.yaml' \\) 2>/dev/null)\nHELM_CHARTS := $(shell find testdata docs/book -type d -path '*/dist/chart' 2>/dev/null)\n\n.PHONY: yamllint yamllint-yaml yamllint-helm\nyamllint: yamllint-yaml yamllint-helm\n\nyamllint-yaml:\n\t@docker run --rm $$(tty -s && echo \"-it\" || echo) -v $(PWD):/data -w /data cytopia/yamllint:latest $(YAMLLINT_FILES) -c .yamllint --no-warnings\n\nyamllint-helm:\n\t@for chart in $(HELM_CHARTS); do \\\n\t  helm template release $$chart --namespace=release-system 2>/dev/null | \\\n\t  docker run --rm -i -v $(PWD):/data -w /data cytopia/yamllint:latest -c .yamllint-helm --no-warnings - || (echo \"yamllint-helm: $$chart failed\"; exit 1); \\\n\tdone\n\n# All Kubebuilder-generated samples (go/v4, kustomize, helm use machinery defaults 0755/0644).\nSAMPLE_ROOTS := testdata \\\n\tdocs/book/src/getting-started/testdata \\\n\tdocs/book/src/cronjob-tutorial/testdata \\\n\tdocs/book/src/multiversion-tutorial/testdata\n\n.PHONY: check-sample-permissions\ncheck-sample-permissions: ## Fail if any file/dir under testdata or docs samples has wrong permissions (expect 0644/0755). bin/ excluded.\n\t@for d in $(SAMPLE_ROOTS); do \\\n\t\ttest -d \"$$d\" || continue; \\\n\t\tbad=$$(find \"$$d\" -path '*/bin' -prune -o \\( \\( -type f ! -perm 0644 \\) -o \\( -type d ! -perm 0755 \\) \\) -print 2>/dev/null); \\\n\t\tif [ -n \"$$bad\" ]; then echo \"Invalid permissions under $$d (expect 0644/0755):\"; echo \"$$bad\"; exit 1; fi; \\\n\tdone\n\n.PHONY: golangci-lint\ngolangci-lint:\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})\n\n.PHONY: apidiff\napidiff: go-apidiff ## Run the go-apidiff to verify any API differences compared with origin/master\n\t$(GO_APIDIFF) master --compare-imports --print-compatible --repo-path=.\n\n.PHONY: go-apidiff\ngo-apidiff:\n\t$(call go-install-tool,$(GO_APIDIFF),github.com/joelanford/go-apidiff,$(GO_APIDIFF_VERSION))\n\n##@ Tests\n\n.PHONY: test\ntest: test-unit test-integration test-testdata test-book test-license test-gomod ## Run the unit and integration tests (used in the CI)\n\n.PHONY: test-unit\nTEST_PKGS := ./pkg/... ./test/e2e/utils/...\ntest-unit: ## Run the unit tests\n\tgo test -race $(TEST_PKGS)\n\n.PHONY: test-integration\ntest-integration: install ## Run the integration tests (requires kubebuilder binary in PATH)\n\tgo test -race -tags=integration -timeout 30m $(TEST_PKGS)\n\n.PHONY: test-coverage\ntest-coverage: ## Run unit and integration tests with coverage report\n\t- rm -rf *.out  # Remove all coverage files if exists\n\tgo test -race -failfast -tags=integration -timeout 30m \\\n\t\t-coverprofile=coverage-all.out \\\n\t\t-coverpkg=\"\\\n./pkg/cli/...,\\\n./pkg/config/...,\\\n./pkg/internal/...,\\\n./pkg/machinery/...,\\\n./pkg/model/...,\\\n./pkg/plugin/...,\\\n./pkg/plugins/golang,\\\n./pkg/plugins/golang/deploy-image/v1alpha1,\\\n./pkg/plugins/golang/v4,\\\n./pkg/plugins/external/...,\\\n./pkg/plugins/common/kustomize/v2,\\\n./pkg/plugins/optional/autoupdate/v1alpha,\\\n./pkg/plugins/optional/grafana/...,\\\n./pkg/plugins/optional/helm/v2alpha/...\" \\\n\t\t$(TEST_PKGS)\n\n.PHONY: check-testdata\ncheck-testdata: ## Run the script to ensure that the testdata is updated\n\t./test/testdata/check.sh\n\n.PHONY: test-testdata\ntest-testdata: ## Run the tests of the testdata directory\n\t./test/testdata/test.sh\n\n.PHONY: test-e2e-local\ntest-e2e-local: ## Run the end-to-end tests locally\n\t## To keep the same kind cluster between test runs, use `SKIP_KIND_CLEANUP=1 make test-e2e-local`\n\t./test/e2e/local.sh\n\n.PHONY: test-e2e-ci\ntest-e2e-ci: ## Run the end-to-end tests (used in the CI)`\n\t./test/e2e/ci.sh\n\n.PHONY: test-book\ntest-book: ## Run the cronjob tutorial's unit tests to make sure we don't break it\n\tcd ./docs/book/src/cronjob-tutorial/testdata/project && make test\n\tcd ./docs/book/src/multiversion-tutorial/testdata/project && make test\n\tcd ./docs/book/src/getting-started/testdata/project && make test\n\n.PHONY: test-license\ntest-license:  ## Run the license check\n\t./test/check-license.sh\n\n.PHONY: test-gomod\ntest-gomod:  ## Run the Go module compatibility check\n\tgo run ./hack/test/check_go_module.go\n\n.PHONY: test-external-plugin\ntest-external-plugin: install  ## Run tests for external plugin\n\tmake -C docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1 install\n\tmake -C docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1 test-plugin\n\n.PHONY: test-spaces\ntest-spaces:  ## Run the trailing spaces check\n\t./test/check_spaces.sh\n\n## TODO: Remove me when go/v4 plugin be removed\n## Deprecated\n.PHONY: test-legacy\ntest-legacy:  ## Run the tests to validate legacy path for webhooks\n\trm -rf  ./testdata/**legacy**/\n\t./test/testdata/legacy-webhook-path.sh\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm locally\n\t@curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash\n\n.PHONY: helm-lint\nhelm-lint: install-helm ## Lint the Helm chart in testdata\n\thelm lint testdata/project-v4-with-plugins/dist/chart\n\n## Tool Binaries\nGO_APIDIFF ?= $(LOCALBIN)/go-apidiff\nGOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nGO_APIDIFF_VERSION ?= v0.8.3\nGOLANGCI_LINT_VERSION ?= v2.8.0\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f $(1) ;\\\nGOBIN=$(LOCALBIN) go install $${package} ;\\\nmv $(1) $(1)-$(3) ;\\\n} ;\\\nln -sf $$(realpath $(1)-$(3)) $(1)\nendef\n"
  },
  {
    "path": "OWNERS",
    "content": "# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md\n\napprovers:\n  - kubebuilder-admins\n  - kubebuilder-approvers\nreviewers:\n  - kubebuilder-admins\n  - kubebuilder-reviewers\n  - kubebuilder-approvers\n"
  },
  {
    "path": "OWNERS_ALIASES",
    "content": "# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md\n\naliases:\n  # active folks who can be contacted to perform admin-related\n  # tasks on the repo, or otherwise approve any PRs.\n  kubebuilder-admins:\n    - camilamacedo86\n    - varshaprasad96\n\n  # non-admin folks who can approve any PRs in the repo\n  # kubebuilder-approvers:\n\n  # folks who can review and LGTM any PRs in the repo (doesn't include\n  # approvers & admins -- those count too via the OWNERS file)\n  kubebuilder-reviewers:\n    - vitorfloriano\n\n  # folks who may have context on ancient history,\n  # but are no longer directly involved\n  kubebuilder-emeritus-approvers:\n  - adirio\n  - directxman12\n  - droot\n  - estroz\n  - jmrodri\n  - joelanford\n  - Kavinjsir\n  - mengqiy\n  - pwittrock\n\n  kubebuilder-emeritus-reviewers:\n  - everettraven\n  - rashmigottipati\n"
  },
  {
    "path": "README.md",
    "content": "[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/kubernetes-sigs/kubebuilder/badge)](https://scorecard.dev/viewer/?uri=github.com/kubernetes-sigs/kubebuilder)\n[![Lint](https://github.com/kubernetes-sigs/kubebuilder/actions/workflows/lint.yml/badge.svg)](https://github.com/kubernetes-sigs/kubebuilder/actions/workflows/lint.yml)\n[![Go Report Card](https://goreportcard.com/badge/sigs.k8s.io/kubebuilder)](https://goreportcard.com/report/sigs.k8s.io/kubebuilder)\n[![Coverage Status](https://coveralls.io/repos/github/kubernetes-sigs/kubebuilder/badge.svg?branch=master)](https://coveralls.io/github/kubernetes-sigs/kubebuilder?branch=master)\n[![Latest release](https://img.shields.io/github/v/release/kubernetes-sigs/kubebuilder)](https://github.com/kubernetes-sigs/kubebuilder/releases)\n[![Go Reference](https://pkg.go.dev/badge/sigs.k8s.io/kubebuilder/v4.svg)](https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4)\n\n## Kubebuilder\n\nKubebuilder is a framework for building Kubernetes APIs using [custom resource definitions (CRDs)](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions).\n\nSimilar to web development frameworks such as *Ruby on Rails* and *SpringBoot*,\nKubebuilder increases velocity and reduces the complexity managed by\ndevelopers for rapidly building and publishing Kubernetes APIs in Go.\nIt builds on top of the canonical techniques used to build the core Kubernetes APIs to provide simple abstractions that reduce boilerplate and toil.\n\nKubebuilder does **not** exist as an example to *copy-paste*, but instead provides powerful libraries and tools\nto simplify building and publishing Kubernetes APIs from scratch. It\nprovides a plugin architecture allowing users to take advantage of optional helpers\nand features. To learn more about this see the [Plugin section][plugin-section].\n\nKubebuilder is developed on top of the [controller-runtime][controller-runtime] and [controller-tools][controller-tools] libraries.\n\n### Kubebuilder is also a library\n\nKubebuilder is extensible and can be used as a library in other projects.\n[Operator-SDK][operator-sdk] is a good example of a project that uses Kubebuilder as a library.\n[Operator-SDK][operator-sdk] uses the plugin feature to include non-Go operators _e.g. operator-sdk's Ansible and Helm-based language Operators_.\n\nTo learn more see [how to create your own plugins][your-own-plugins].\n\n### Installation\n\nIt is strongly recommended that you use a released version. Release binaries are available on the [releases](https://github.com/kubernetes-sigs/kubebuilder/releases) page.\nFollow the [instructions](https://book.kubebuilder.io/quick-start.html#installation) to install Kubebuilder.\n\n## Getting Started\n\nSee the [Getting Started](https://book.kubebuilder.io/quick-start.html) documentation.\n\nAlso, ensure that you check out the [Deploy Image](./docs/book/src/plugins/available/deploy-image-plugin-v1-alpha.md)\nPlugin. This plugin allows users to scaffold API/Controllers to deploy and manage an\nOperand (image) on the cluster following the guidelines and best practices. It abstracts the\ncomplexities of achieving this goal while allowing users to customize the generated code.\n\n## Documentation\n\nCheck out the Kubebuilder [book](https://book.kubebuilder.io).\n\n## Resources\n\n- Kubebuilder Book: [book.kubebuilder.io](https://book.kubebuilder.io)\n- GitHub Repo: [kubernetes-sigs/kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)\n- Slack channel: [#kubebuilder](https://kubernetes.slack.com/messages/#kubebuilder)\n- Google Group: [kubebuilder@googlegroups.com](https://groups.google.com/forum/#!forum/kubebuilder)\n- Design Documents: [designs](designs/)\n- Plugin: [plugins][plugin-section]\n\n## Motivation\n\nBuilding Kubernetes tools and APIs involves making a lot of decisions and writing a lot of boilerplate.\n\nTo facilitate easily building Kubernetes APIs and tools using the canonical approach, this framework\nprovides a collection of Kubernetes development tools to minimize toil.\n\nKubebuilder attempts to facilitate the following developer workflow for building APIs\n\n1. Create a new project directory\n2. Create one or more resource APIs as CRDs and then add fields to the resources\n3. Implement reconcile loops in controllers and watch additional resources\n4. Test by running against a cluster (self-installs CRDs and starts controllers automatically)\n5. Update bootstrapped integration tests to test new fields and business logic\n6. Build and publish a container from the provided Dockerfile\n\n## Scope\n\nBuilding APIs using CRDs, Controllers, and Admission Webhooks.\n\n## Philosophy\n\nSee [DESIGN.md](DESIGN.md) for the guiding principles of the various Kubebuilder projects.\n\nTL;DR:\n\nProvide clean library abstractions with clear and well-exampled go docs.\n\n- Prefer using go *interfaces* and *libraries* over-relying on *code generation*\n- Prefer using *code generation* over *1 time init* of stubs\n- Prefer *1 time init* of stubs over forked and modified boilerplate\n- Never fork and modify boilerplate\n\n## Techniques\n\n- Provide higher-level libraries on top of low-level client libraries\n  - Protect developers from breaking changes in low-level libraries\n  - Start minimal and provide progressive discovery of functionality\n  - Provide sane defaults and allow users to override when they exist\n- Provide code generators to maintain common boilerplate that can't be addressed by interfaces\n  - Driven off of `// +` comments\n- Provide bootstrapping commands to initialize new packages\n\n## Versioning and Releasing\n\nSee [VERSIONING.md](VERSIONING.md).\n\n## Troubleshooting\n\n- ### Bugs and Feature Requests:\n  If you have what looks like a bug, or you would like to make a feature request, please use the [Github issue tracking system.](https://github.com/kubernetes-sigs/kubebuilder/issues)\nBefore you file an issue, please search existing issues to see if your issue is already covered.\n\n- ### Slack\n  For real-time discussion,  you can join the [#kubebuilder](https://slack.k8s.io/#kubebuilder) slack channel. Slack requires registration, but the Kubernetes team is an open invitation to anyone to register here. Feel free to come and ask any questions.\n\n## Contributing\n\nContributions are greatly appreciated. The maintainers actively manage the issues list and try to highlight issues suitable for newcomers.\nThe project follows the typical GitHub pull request model. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.\nBefore starting any work, please either comment on an existing issue or file a new one.\n\n## Operating Systems Supported\n\nCurrently, Kubebuilder officially supports macOS and Linux platforms. If you are using a Windows OS, we recommend you read the instructions in [here](docs/windows.md).\n\nContributions towards supporting Windows are not planned.\n\n## Versions Compatibility and Supportability\n\nProjects created by Kubebuilder contain a `Makefile` that installs tools at versions defined during project creation. The main tools included are:\n\n- [kustomize](https://github.com/kubernetes-sigs/kustomize)\n- [controller-gen](https://github.com/kubernetes-sigs/controller-tools)\n- [setup-envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/main/tools/setup-envtest)\n\nAdditionally, these projects include a `go.mod` file specifying dependency versions.\nKubebuilder relies on [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) and its Go and Kubernetes dependencies.\nTherefore, the versions defined in the `Makefile` and `go.mod` files are the ones that have been tested, supported, and recommended.\n\nEach minor version of Kubebuilder is tested with a specific minor version of the client-go.\nWhile a Kubebuilder minor version *may* be compatible with other client-go minor versions,\nor other tools this compatibility is not guaranteed, supported, or tested.\n\nThe minimum Go version required by Kubebuilder is determined by the highest minimum\nGo version required by its dependencies. This is usually aligned with the minimum\nGo version required by the corresponding `k8s.io/*` dependencies.\n\nCompatible `k8s.io/*` versions, client-go versions, and minimum Go versions can be found in the `go.mod`\nfile scaffolded for each project for each [tag release](https://github.com/kubernetes-sigs/kubebuilder/tags).\n\n**Example:** For the `4.1.1` release, the minimum Go version compatibility is `1.22`.\nYou can refer to the samples in the testdata directory of the tag released [v4.1.1](https://github.com/kubernetes-sigs/kubebuilder/tree/v4.1.1/testdata),\nsuch as the [go.mod](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.1.1/testdata/project-v4/go.mod#L3) file for `project-v4`. You can also check the versions of the tools supported and\ntested for this release by examining the [Makefile](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.1.1/testdata/project-v4/Makefile#L160-L165).\n\n## Community Meetings\n\nThe following meetings happen biweekly:\n\n- Kubebuilder Meeting\n\nYou are more than welcome to attend. For further info join to [kubebuilder@googlegroups.com](https://groups.google.com/g/kubebuilder).\nEvery month, our team meets on the first Thursday at 11:00 PT (Pacific Time) to discuss our progress and plan for the upcoming weeks.\nPlease note that we have been syncing more frequently offline via Slack lately. However, if you add a topic to the agenda, we will hold the meeting as scheduled.\nAdditionally, we can use this channel to demonstrate new features.\n\n[operator-sdk]: https://github.com/operator-framework/operator-sdk\n[plugin-section]: https://book.kubebuilder.io/plugins/plugins.html\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[your-own-plugins]: https://book.kubebuilder.io/plugins/extending\n[controller-tools]: https://github.com/kubernetes-sigs/controller-tools\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Release Process\n\nThe Kubebuilder Project is released on an as-needed basis. The process is as follows:\n\n**Note:** Releases are done from the `release-MAJOR.MINOR` branches. For PATCH releases it is not required\nto create a new branch. Instead, you will just need to ensure that all major fixes are cherry-picked into the respective\n`release-MAJOR.MINOR` branch. To know more about versioning, check https://semver.org/.\n\n**Note:** Before `3.5.*` release this project was released based on `MAJOR`. A change to the\nprocess was done to ensure that we have an aligned process under the org (similar to `controller-runtime` and\n`controller-tools`) and to make it easier to produce patch releases.\n\n## How to do a release\n\n### Create the new branch and the release tag\n\n1. Create a new branch `git checkout -b release-<MAJOR.MINOR>` from master\n2. Push the new branch to the remote repository\n\n### Now, let's generate the changelog\n\n1. Create the changelog from the new branch `release-<MAJOR.MINOR>` (`git checkout release-<MAJOR.MINOR>`).\n   You will need to use the [kubebuilder-release-tools][kubebuilder-release-tools] to generate release notes. See [here][release-notes-generation]\n\n> **Note**\n> - You will need to have checkout locally from the remote repository the previous branch\n> - Also, ensure that you fetch all tags from the remote `git fetch --all --tags`\n> - Also, if you face issues to generate the release notes you might want to able to sort it out by running i.e.:\n> `go run sigs.k8s.io/kubebuilder-release-tools/notes --use-upstream=false --from=v3.11.0 --branch=release-X`\n\n\n### Draft a new release from GitHub\n\n1. Create a new tag with the correct version from the new `release-<MAJOR.MINOR>` branch\n2. Verify the Release Github Action. It should build the assets and publish in the draft release\n3. You also need to manually add the changelog generated above on the release page and publish it. Now, the source code is released!\n\n### Update the website docs (https://book.kubebuilder.io/quick-start.html)\n\n1. Push a PR to update the `book-v3` branch with the changes of the latest release branch created (`release-<MAJOR.MINOR>`)\n2. Ping in the [Kubebuilder Slack channel](https://kubernetes.slack.com/archives/CAR30FCJZ) and ask for reviews.\n\n### When the release be done and the website update: Announce the new release:\n\n1. Announce the new release on the Slack channel, i.e:\n\n````\n:announce: Kubebuilder v3.5.0 has been released!\nThis release includes a Kubernetes dependency bump to v1.24.\nFor more info, see the release page: https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v3.5.0\n :tada:  Thanks to all our contributors!\n````\n\n2. Announce the new release via email is sent to `kubebuilder@googlegroups.com` with the subject `[ANNOUNCE] Kubebuilder $VERSION is released`\n\n\n## HEAD releases\n\nThe binaries releases for HEAD are available here:\n\n- [kubebuilder-release-master-head-darwin-amd64.tar.gz](https://storage.googleapis.com/kubebuilder-release/kubebuilder-release-master-head-darwin-amd64.tar.gz)\n- [kubebuilder-release-master-head-linux-amd64.tar.gz](https://storage.googleapis.com/kubebuilder-release/kubebuilder-release-master-head-linux-amd64.tar.gz)\n\n## How the releases are configured\n\nThe releases occur in an account in the Google Cloud (See [here](https://console.cloud.google.com/cloud-build/builds?project=kubebuilder)) using Cloud Build.\n\n### To build the Kubebuilder CLI binaries:\n\nA trigger GitHub action [release](.github/workflows/release.yml) is trigged when a new tag is pushed.\nThis action will call the job [./build/.goreleaser.yml](./build/.goreleaser.yml).\n\n###  (Deprecated) - To build the Kubebuilder-tools: (Artifacts required to use ENV TEST)\n\n> We no longer build the artifacts and the promotion of those is deprecated. For more info\nsee: https://github.com/kubernetes-sigs/kubebuilder/discussions/4082\n\nKubebuilder projects requires artifacts which are used to do test with ENV TEST (when we call `make test` target)\nThese artifacts can be checked in the service page: https://storage.googleapis.com/kubebuilder-tools\n\nThe build is made from the branch [tools-releases](https://github.com/kubernetes-sigs/kubebuilder/tree/tools-releases) and the trigger will call the `build/cloudbuild_tools.yaml` passing\nas argument the architecture and the OS that should be used, e.g:\n\n<img width=\"553\" alt=\"Screenshot 2022-04-30 at 10 15 41\" src=\"https://user-images.githubusercontent.com/7708031/166099666-ae9cd2df-73fe-47f6-a987-464f63df9a19.png\">\n\nFor further information see the [README](https://github.com/kubernetes-sigs/kubebuilder/blob/tools-releases/README.md).\n\n### (Deprecated) - To build the `kube-rbac-proxy` images:\n\n> We no longer build the images and the promotion of those images is deprecated. For more info\nsee: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907\n\nThese images are built from the project [brancz/kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy).\nThe projects built with Kubebuilder creates a side container with `kube-rbac-proxy` to protect the Manager.\n\nThese images can be checked in the console, see [here](https://console.cloud.google.com/gcr/images/kubebuilder/GLOBAL/kube-rbac-proxy).\n\nThe project `kube-rbac-proxy` is in the process to be donated to the k8s org. However, it is going on for a long time and then,\nwe have no ETA for that to occur. When that occurs we can automate this process. But until there we need to generate these images\nby bumping the versions/tags released by `kube-rbac-proxy` on the branch\n[kube-rbac-proxy-releases](https://github.com/kubernetes-sigs/kubebuilder/tree/kube-rbac-proxy-releases)\nthen the `build/cloudbuild_kube-rbac-proxy.yaml` will generate the images.\n\nTo check an example, see the pull request [#2578](https://github.com/kubernetes-sigs/kubebuilder/pull/2578).\n\n**Note**: we cannot use the images produced by the project `kube-rbac-proxy` because we need to ensure\nto Kubebuilder users that these images will be available.\n\n### (Deprecated) - To build the `gcr.io/kubebuilder/pr-verifier` images:\n\n> We are working on to move all out from GCP Kubebuilder project. For further information see: https://github.com/kubernetes/k8s.io/issues/2647#issuecomment-2111182864\n\nThese images are used to verify the PR title and description. They are built from [kubernetes-sigs/kubebuilder-release-tools](https://github.com/kubernetes-sigs/kubebuilder-release-tools/).\nIn Kubebuilder, we have been using this project via the GitHub action [.github/workflows/verify.yml](.github/workflows/verify.yml)\nand not the image, see:\n\n```yaml\n  verify:\n    name: Verify PR contents\n    runs-on: ubuntu-latest\n    steps:\n    - name: Verifier action\n      id: verifier\n      uses: kubernetes-sigs/kubebuilder-release-tools@v0.1.1\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n```\n\nHowever, the image should still be built and maintained since other projects under the org might be using them.\n\n[kubebuilder-release-tools]: https://github.com/kubernetes-sigs/kubebuilder-release-tools\n[release-notes-generation]: https://github.com/kubernetes-sigs/kubebuilder-release-tools/blob/master/README.md#release-notes-generation\n[release-process]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/VERSIONING.md#releasing\n"
  },
  {
    "path": "SECURITY_CONTACTS",
    "content": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Team to reach out\n# to for triaging and handling of incoming issues.\n#\n# The below names agree to abide by the\n# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)\n# and will be removed and replaced if they violate that agreement.\n#\n# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE\n# INSTRUCTIONS AT https://kubernetes.io/security/\n\ncamilamacedo86\nvarshaprasad96\n"
  },
  {
    "path": "VERSIONING.md",
    "content": "# Versioning and Releasing for Kubebuilder\n\nWe (mostly) follow the [common Kubebuilder versioning\nguidelines][guidelines], and use the corresponding tooling and PR process\ndescribed there.\n\nFor the purposes of the aforementioned guidelines, Kubebuilder counts as\na \"CLI project\".\n\n[guidelines]: https://sigs.k8s.io/kubebuilder-release-tools/VERSIONING.md\n\n## Compatibility\n\nNote that we generally do not support older release branches, except in\nextreme circumstances.\n\nBear in mind that changes to scaffolding generally constitute breaking\nchanges -- see [below](#understanding-the-versions) for more details.\n\n## Releasing\n\nWhen releasing, you'll need to:\n\n- to update references in [the build directory](build/) to the latest\n  version of the [envtest tools](#tools-releases) **before tagging the\n  release.**\n\n- reset the book branch: see [below](#book-releases)\n\nYou may also want to check that the book is generating the marker docs off\nthe latest controller-tools release.  That info is stored in\n[docs/book/install-and-build.sh](/docs/book/install-and-build.sh).\n\n## Book Releases\n\nThe book's main version (https://book.kubebuilder.io) is published off of\nthe [book-v3][book-branch] (a version built off the main branch can be\nfound at https://master.book.kubebuilder.io).\n\nDocs changes that aren't specific to a new feature should be\ncherry-picked to the aforementioned branch to get them to be published.\nThe cherry-picks will automatically be published to the book once their PR\nmerges.\n\n**When you publish a Kubebuilder release**, be sure to also submit a PR\nthat merges the main branch into [book-v3][book-branch], so that it\ndescribes the latest changes in the new release.\n\n[book-branch]: https://github.com/kubernetes-sigs/kubebuilder/tree/tools-releases\n\n## Tools Releases\n\nIn order to update the [envtest tools][envtest-ref], you'll need to do an\nupdate to the [tools-releases branch][tools-branch].  Simply submit a PR\nagainst that branch that changes all references to the current version to\nthe desired next version.  Once the PR is merged, Google Cloud Build will\ntake care of building and publishing the artifacts.\n\n[envtest-ref]: https://book.kubebuilder.io/reference/artifacts.html\n[tools-branch]: https://github.com/kubernetes-sigs/kubebuilder/tree/tools-releases\n[kb-releases]:https://github.com/kubernetes-sigs/kubebuilder/releases\n[cli-plugins-versioning]:docs/book/src/plugins/extending#plugin-versioning\n"
  },
  {
    "path": "build/.goreleaser.yml",
    "content": "#  Copyright 2020 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\n# This is a GoReleaser configuration file for Kubebuilder release.\n# Make sure to check the documentation at http://goreleaser.com\n\n# Global environment variables that are needed for hooks and builds.\nversion: 2\n\nenv:\n  - GO111MODULE=on\n\n# Hooks to run before any build is run.\nbefore:\n  hooks:\n    - go mod download\n\n# Build a binary for each target in targets.\nbuilds:\n  - id: kubebuilder\n    binary: kubebuilder\n    mod_timestamp: \"{{ .CommitTimestamp }}\"\n    targets:\n      - linux_amd64\n      - linux_arm64\n      - linux_ppc64le\n      - linux_s390x\n      - darwin_amd64\n      - darwin_arm64\n    env:\n      - CGO_ENABLED=0\n\n# Only binaries of the form \"kubebuilder_${goos}_${goarch}\" will be released.\narchives:\n  - formats: ['binary']\n    # Setting name_template correctly maps checksums to binary names.\n    name_template: \"{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}\"\n\n# Checksum all binaries.\nchecksum:\n  name_template: \"checksums.txt\"\n\n# kubebuilder uses a custom changelog, so leave this empty.\nchangelog:\n\n# github.com/kubernetes-sigs/kubebuilder\nrelease:\n  github:\n    owner: kubernetes-sigs\n    name: kubebuilder\n\n# Add the SBOM configuration at the end to generate SBOM files\nsboms:\n  - id: kubebuilder-sbom\n    artifacts: binary\n    cmd: syft\n    args: [\"$artifact\", \"--output\", \"cyclonedx-json=$document\"]\n    documents:\n      - \"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.cyclonedx.sbom.json\"\n"
  },
  {
    "path": "code-of-conduct.md",
    "content": "# Kubernetes Community Code of Conduct\n\nPlease refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)"
  },
  {
    "path": "designs/README.md",
    "content": "Designs\n=======\n\nThese are design documents for changes to Kubebuilder (and\ncross-repository changes for related projects, like controller-runtime and\ncontroller-tools). They exist to help document the design processes that\ngo into writing Kubebuilder, but may not be up-to-date (more below).\n\nNot all changes to Kubebuilder need a design document -- only major ones.\nUse your best judgement.\n\nWhen submitting a design document, we encourage having written\na proof-of-concept, and it's perfectly acceptable to submit the\nproof-of-concept PR simultaneously with the design document, as the\nproof-of-concept process can help iron out wrinkles and can help with the\n`Example` section of the template.\n\n## Out-of-Date Designs\n\n**Kubebuilder documentation (the [book](https://book.kubebuilder.io) and\nthe [GoDoc](https://pkg.go.dev/sigs.k8s.io/controller-runtime?tab=doc)) should be\nconsidered the canonical, update-to-date reference and architectural\ndocumentation** for Kubebuilder.\n\nHowever, if you see an out-of-date design document, feel free to submit\na PR marking it as such, and add an addendum linking to issues documenting\nwhy things changed.  For example:\n\n```markdown\n\n# Out of Date\n\nThis change is out of date.  It turns out curly braces are frustrating to\ntype, so we had to abandon functions entirely, and have users specify\ncustom functionality using strings of Common LISP instead.  See #000 for\nmore information.\n```\n"
  },
  {
    "path": "designs/code-generate-image-plugin.md",
    "content": "| Authors       | Creation Date | Status      | Extra |\n|---------------|---------------|-------------|---|\n| @camilamacedo86 | 2021-02-14 | Implemented | [deploy-image-plugin-v1-alpha](../docs/book/src/plugins/available/deploy-image-plugin-v1-alpha.md) |\n\n# New Plugin (`deploy-image.go.kubebuilder.io/v1beta1`) to generate code\n\n## Summary\n\nThis proposal defines a new plugin that allows users to get the scaffold with the\n required code to have a project that will deploy and manage an image on the cluster following the guidelines and what have been considered as good practices.\n\n## Motivation\n\nThe biggest part of the Kubebuilder users looking for to create a project that will at the end only deploy an image. In this way, one of the  mainly motivations of this proposal is to abstract the complexities to achieve this goal and still giving the possibility of users improve and customize their projects according to their requirements.\n\n**Note:** This plugin will address requests that has been raised for a while and for many users in the community. Check [here](https://github.com/operator-framework/operator-sdk/pull/2158), for example, a request done in the past for the SDK project which is integrated with Kubebuidler to address the same need.\n\n### Goals\n\n- Add a new plugin to generate the code required to deploy and manage an image on the cluster\n- Promote the best practices by giving examples of common implementations\n- Make the process of developing  operator's projects easier and more agile.\n- Give flexibility to the users and allow them to change the code according to their needs\n- Provide examples of code implementations and of the usage of the most common features and reduce the learning curve\n\n### Non-Goals\n\nThe idea of this proposal is to provide a facility for the users. This plugin can be improved\nin the future, however, this proposal just covers the basic requirements. In this way, is a non-goal\nallow extra configurations such as; scaffolding the project using webhooks and the controller covered by tests.\n\n## Proposal\n\nAdd the new plugin code generated which will scaffold code implementation to deploy the image informed which would like such as; `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1` which will:\n\n- Add a code implementation that will do the Custom Resource reconciliation and create a Deployment resource for the `--image`;\n\n- Add an EnvVar on the manager manifest (`config/manager/manager.yaml`) which will store the image informed and show its possibility to users:\n\n```yaml\n    ..\n    spec:\n      containers:\n        - name: manager\n          env:\n            - name: {{ resource}}-IMAGE\n              value: {{image:tag}}\n          image: controller:latest\n      ...\n```\n\n- Add a check into reconcile to ensure that the replicas of the deployment on the cluster are equal the size defined in the CR:\n\n```go\n\t// Ensure the deployment size is the same as the spec\n\tsize := {{ resource }}.Spec.Size\n\tif *found.Spec.Replicas != size {\n\t\tfound.Spec.Replicas = &size\n\t\terr = r.Update(ctx, found)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\", \"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t\t// Spec updated - return and requeue\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n```\n\n- Add the watch feature for the Deployment managed by the controller:\n\n```go\nfunc (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&cachev1alpha1.{{ resource }}{}).\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n```\n\n- Add the RBAC permissions required for the scenario such as:\n\n```go\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n- A status [conditions][conditions] to allow users to check if the deployment occurred successfully or if its errors\n\n- Add a [marker][markers] in the spec definition to demonstrate how to use OpenAPI schemas validation such as `+kubebuilder:validation:Minimum=1`\n\n- Add the specs on the `_types.go` to generate the CRD/CR sample with default values for `ImagePullPolicy` (`Always`), `ContainerPort` (`80`) and the `Replicas Size` (`3`)\n\n- Add a finalizer implementation with TODO for the CR managed by the controller such as described in the SDK doc [Handle Cleanup on Deletion](https://sdk.operatorframework.io/docs/building-operators/golang/advanced-topics/#handle-cleanup-on-deletion)\n\n### User Stories\n\n- I am a user, who would like to use a command to scaffold my common need which is to deploy an image of my application, so that I do not need to know exactly how to implement it\n\n- I am a user, would like to have a good example code base that uses the common features so that I can easily learn its concepts and have a good starting point to address my needs.\n\n- I am as maintainer, would like to have a good example to address the common questions, so that I can easily describe how to implement the projects and/or use the common features.\n\n### Implementation Details/Notes/Constraints\n\n**Example of the controller template**\n\n```go\n// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }},verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/finalizers,verbs=update\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n\nfunc (r *{{ resource }}.Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {\n\tctx := context.Background()\n\tlog := r.Log.WithValues(\"{{ resource }}\", req.NamespacedName)\n\n\t// Fetch the {{ resource }} instance\n\t{{ resource }} := &{{ apiimportalias }}.{{ resource }}{}\n\terr := r.Get(ctx, req.NamespacedName, {{ resource }})\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\tlog.Info(\"{{ resource }} resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get {{ resource }}\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: {{ resource }}.Name, Namespace: {{ resource }}.Namespace}, found)\n\tif err != nil && errors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep := r.deploymentFor{{ resource }}({{ resource }})\n\t\tlog.Info(\"Creating a new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\terr = r.Create(ctx, dep)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t\t// Deployment created successfully - return and requeue\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Ensure the deployment size is the same as the spec\n\tsize := {{ resource }}.Spec.Size\n\tif *found.Spec.Replicas != size {\n\t\tfound.Spec.Replicas = &size\n\t\terr = r.Update(ctx, found)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\", \"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t\t// Spec updated - return and requeue\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n    // TODO: add here code implementation to update/manage the status\n\n\treturn ctrl.Result{}, nil\n}\n\n// deploymentFor{{ resource }} returns a {{ resource }} Deployment object\nfunc (r *{{ resource }}Reconciler) deploymentFor{{ resource }}(m *{{ apiimportalias }}.{{ resource }}) *appsv1.Deployment {\n\tls := labelsFor{{ resource }}(m.Name)\n\treplicas := m.Spec.Size\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      m.Name,\n\t\t\tNamespace: m.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: &replicas,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:   imageFor{{ resource }}(m.Name),\n\t\t\t\t\t\tName:    {{ resource }},\n                        ImagePullPolicy: {{ resource }}.Spec.ContainerImagePullPolicy,\n\t\t\t\t\t\tCommand: []string{\"{{ resource }}\"},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: {{ resource }}.Spec.ContainerPort,\n\t\t\t\t\t\t\tName:          \"{{ resource }}\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t// Set {{ resource }} instance as the owner and controller\n\tctrl.SetControllerReference(m, dep, r.Scheme)\n\treturn dep\n}\n\n// labelsFor{{ resource }} returns the labels for selecting the resources\n// belonging to the given {{ resource }} CR name.\nfunc labelsFor{{ resource }}(name string) map[string]string {\n\treturn map[string]string{\"type\": \"{{ resource }}\", \"{{ resource }}_cr\": name}\n}\n\n// imageFor{{ resource }} returns the image for the resources\n// belonging to the given {{ resource }} CR name.\nfunc imageFor{{ resource }}(name string) string {\n\t// TODO: this method will return the value of the envvar create to store the image:tag informed\n}\n\nfunc (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&cachev1alpha1.{{ resource }}{}).\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n\n```\n\n**Example of the spec for the <kind>_types.go template**\n\n```go\n// {{ resource }}Spec defines the desired state of {{ resource }}\ntype {{ resource }}Spec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n    // +kubebuilder:validation:Minimum=1\n\t// Size defines the number of {{ resource }} instances\n\tSize int32 `json:\"size,omitempty\"`\n\n    // ImagePullPolicy defines the policy to pull the container images\n\tImagePullPolicy string `json:\"image-pull-policy,omitempty\"`\n\n    // ContainerPort specifies the port which will be used by the image container\n\tContainerPort int `json:\"container-port,omitempty\"`\n\n}\n```\n\n## Design Details\n\n### Test Plan\n\nTo ensure this implementation a new project example should be generated in the [testdata](../testdata/) directory of the project. See the [test/testdata/generate.sh](../test/testadata/generate.sh). Also, we should use this scaffold in the [integration tests](../test/e2e/) to ensure that the data scaffold works on the cluster as expected.\n\n### Graduation Criteria\n\n- The new plugin will only support `project-version=3`\n- The attribute image with the value informed should be added to the resources model in the PROJECT file to let the tool know that the Resource gets done with the common basic code implementation:\n\n```yaml\nplugins:\n    deploy-image.go.kubebuilder.io/v1beta1:\n        resources:\n          - domain: example.io\n            group: crew\n            kind: Captain\n            version: v1\n            image: \"<some-registry>/<project-name>:<tag>\n```\n\nFor further information check the definition agreement register in the comment https://github.com/kubernetes-sigs/kubebuilder/issues/1941#issuecomment-778649947.\n\n## Open Questions\n\n1. Should we allow to scaffold the code for an API that is already created for the project?\nNo, at least in the first moment to keep the simplicity.\n\n2. Should we support StatefulSet and Deployments?\nThe idea is we start it by using a Deployment. However, we can improve the feature in follow-ups to support more default types of scaffolds which could be like `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1 --type=[deployment|statefulset|webhook]`\n\n3. Could this feature be useful to other languages or is it just valid to Go-based operators?\n\nThis plugin would is reponsable to scaffold content and files for Go-based operators. In a future, if other language-based operators starts to be supported (either officially or by the community) this plugin could be used as reference to create an equivalent one for their languages. Therefore, it should probably not to be a `subdomain` of `go.kubebuilder.io.`\n\nFor its integration with SDK, it might be valid for the Ansible-based operators where a new `playbook/role` could be generated as well. However, for example,for  the Helm plugin, it might be useless. E.g `deploy-image.ansible.sdk.operatorframework.io/v1beta1`\n\n4. Should we consider creating a separate repo for plugins?\n\nIn the long term yes. However, see that currently, Kubebuilder does not have too many plugins yet. And then, the preliminary support for plugins was not indeed released. For more info see the [Extensible CLI and Scaffolding Plugins][plugins-phase1-design-doc].\n\nIn this way, at this moment, it shows to be a little Premature Optimization. Note that the issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/1378) will check the possibility of the plugins be as separate binaries that can be discovered by the Kubebuilder CLI binary via user-specified plugin file paths. Then, the discussion over the best approach to dealing with many plugins and if they should or not leave in the Kubebuilder repository would be better addressed after that.\n\n5. Is Kubebuilder prepared to receive this implementation already?\n\nThe [Extensible CLI and Scaffolding Plugins - Phase 1.5](extensible-cli-and-scaffolding-plugins-phase-1-5.md) and issue #1941 are required to be implemented before this proposal. Also, to have a better idea of the proposed solutions made so for the Plugin Ecosystem see the meta issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/2016)\n\n[markers]: ../docs/book/src/reference/markers.md\n[conditions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n[plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md\n"
  },
  {
    "path": "designs/crd_version_conversion.md",
    "content": "| Authors       | Creation Date | Status      | Extra |\n|---------------|---------------|-------------|-------|\n| @droot | 01/30/2019| implementable | -     |\n\n# API Versioning in Kubebuilder\n\nThis document describes high level design and workflow for supporting multiple versions in an API built using Kubebuilder. Multi-version support was added as an alpha feature in kubernetes project in 1.13 release. Here are links to some recommended reading material.\n\n* [CRD version Conversion Design Doc](https://github.com/kubernetes/community/blob/3f8bf88a06a114b3984417d6867bb16506c9c71e/contributors/design-proposals/api-machinery/customresource-conversion-webhook.md)\n\n* [CRD Webhook Conversion API changes PR](https://github.com/kubernetes/kubernetes/pull/67795/files)\n\n* [CRD Webhook Conversion PR](https://github.com/kubernetes/kubernetes/pull/67006)\n\n* [Kubecon talk](https://www.youtube.com/watch?v=HsYtMvvzDyI&t=0s&index=100&list=PLj6h78yzYM2PZf9eA7bhWnIh_mK1vyOfU)\n\n* [CRD version conversion POC](https://github.com/droot/crd-conversion-example)\n\n# Design\n\n## Hub and Spoke\n\nThe basic concept is that all versions of an object share the storage. So say if you have versions v1, v2 and v3 of a Kind Toy, kubernetes will use one of the versions to persist the object in stable storage i.e. Etcd. User can specify the version to be used for storage in the Custom Resource definition for that API.\n\nOne can think storage version as the hub and other versions as spoke to visualize the relationship between storage and other versions (as shown below in the diagram). The key thing to note is that conversion between storage and other version should be lossless (round trippable). As shown in the diagram below, v3 is the storage/hub version and v1, v2 and v4 are spoke version. The document uses storage version and hub interchangeably.\n\n![hub and spoke version diagram][version-diagram]\n\nSo if each spoke version (v1, v2 and v4 in this case) defines conversion function from/to the hub version, then conversion function between the spoke versions (v1, v2, v4) can be derived. For example, for converting an object from v1 to v4, we can convert v1 to v3 (the hub version) and v3 to v4.\n\nWe will introduce two interfaces in controller-runtime to express the above relationship.\n\n```Go\n// Hub defines capability to indicate whether a versioned type is a Hub or not.\n\ntype Hub interface {\n    runtime.Object\n    Hub()\n}\n\n// A versioned type is convertible if it can be converted to/from a hub type.\n\ntype Convertible interface {\n    runtime.Object\n    ConvertTo(dst Hub) error\n    ConvertFrom(src Hub) error\n}\n```\n\nA spoke type needs to implement Convertible interface. Kubebuilder can scaffold the skeleton for a type when it is created. An example of Convertible implementation:\n\n```Go\npackage v1\n\nfunc (ej *ExternalJob) ConvertTo(dst conversion.Hub) error {\n    switch t := dst.(type) {\n    case *v3.ExternalJob:\n        jobv3 := dst.(*v3.ExternalJob)\n        jobv3.ObjectMeta = ej.ObjectMeta\n         // conversion implementation\n\t   //\n        return nil\n    default:\n        return fmt.Errorf(\"unsupported type %v\", t)\n    }\n}\n\nfunc (ej *ExternalJob) ConvertFrom(src conversion.Hub) error {\n    switch t := src.(type) {\n    case *v3.ExternalJob:\n        jobv3 := src.(*v3.ExternalJob)\n        ej.ObjectMeta = jobv3.ObjectMeta\n\t   // conversion implementation\n        return nil\n    default:\n        return fmt.Errorf(\"unsupported type %v\", t)\n    }\n}\n```\n\nThe storage type v3 needs to implement the Hub interface:\n\n```Go\n\npackage v3\nfunc (ej *ExternalJob) Hub() {}\n\n```\n## Conversion Webhook Handler\n\nController-runtime will implement a default conversion handler that can handle conversion requests for any API type. Code snippets below captures high level implementation details of the handler. This handler will be registered with the webhook server by default.\n```Go\n\ntype conversionHandler struct {\n\t// scheme which has Go types for the APIs are registered. This will be injected by controller manager.\n\tScheme runtime.Scheme\n\t// decoder which will be injected by the webhook server\n\t// decoder knows how to decode a conversion request and API objects.\n\tDecoder decoder.Decoder\n}\n\n// This is the default handler which will be mounted on the webhook server.\nfunc (ch *conversionHandler) Handle(r *http.Request, w http.Response) {\n\t// decode the request to converReview request object\n\tconvertReq := ch.Decode(r.Body)\n\tfor _, obj := range convertReq.Objects {\n\t// decode the incoming object\n\tsrc, gvk, _ := ch.Decoder.Decode(obj.raw)\n\n\t// get target object instance for convertReq.DesiredAPIVersion and gvk.Kind\n\tdst, _ := getTargetObject(convertReq.DesiredAPIVersion, gvk.Kind)\n\n\t// this is where conversion between objects happens\n\n\tch.ConvertObject(src, dst)\n\n\t// append dst to converted object list\n}\n\n\t// create a conversion response with converted objects\n}\n\nfunc (ch *conversionHandler) convertObject(src, dst runtime.Object) error {\n    // check if src and dst are of same type, then may be return with error because API server will never invoke this handler for same version.\n    srcIsHub, dstIsHub := isHub(src), isHub(dst)\n    srcIsConvertible, dstIsConvertible := isConvertible(src), isConvertable(dst)\n    if srcIsHub {\n        if dstIsConvertible {\n            return dst.(conversion.Convertable).ConvertFrom(src.(conversion.Hub))\n        } else {\n            // this is error case, this can be flagged at setup time ?\n            return fmt.Errorf(\"%T is not convertible to\", src)\n        }\n    }\n\n    if dstIsHub {\n        if srcIsConvertible {\n            return src.(conversion.Convertable).ConvertTo(dst.(conversion.Hub))\n        } else {\n            // this is error case.\n            return fmt.Errorf(\"%T is not convertible\", src)\n        }\n    }\n\n    // neither src or dst are Hub, means both of them are spoke, so lets get the hub\n    // version type.\n\n    hub, err := getHub(scheme, src)\n    if err != nil {\n        return err\n    }\n\n    // shall we get Hub for dst type as well and ensure hubs are same ?\n    // src and dst needs to be convertible for it to work\n    if !srcIsConvertable || !dstIsConvertable {\n        return fmt.Errorf(\"%T and %T needs to be both convertible\", src, dst)\n    }\n\n    err = src.(conversion.Convertible).ConvertTo(hub)\n    if err != nil {\n        return fmt.Errorf(\"%T failed to convert to hub version %T : %w\", src, hub, err)\n    }\n\n    err = dst.(conversion.Convertible).ConvertFrom(hub)\n    if err != nil {\n        return fmt.Errorf(\"%T failed to convert from hub version %T : %w\", dst, hub, err)\n    }\n    return nil\n}\n```\n\nHandler Registration flow will perform following at the startup:\n\n* For APIs with hub defined, it can examine if spoke versions implement convertible or not and can abort with error.\n\n* It will also be nice if we can detect an API with multiple versions but with no hub defined, but that requires distinguishing between APIs defined in the project vs external.\n\n# CRD Generation\n\nThe tool that generates the CRD manifests lives under controller-tools repo. Currently it generates the manifests for each <group, version, kind> discovered under ‘pkg/…’ directory in the project by examining the comments (aka annotations) in Go source files. Following annotations will be added to support multi version:\n\n## Storage/Serve annotations:\n\nThe resource annotation will be extended to indicate storage/serve attributes as shown below.\n\n```Go\n// ...\n// +kubebuilder:resource:storage=true,serve=true\n// …\ntype APIName struct {\n   ...\n}\n```\n\nThe default value of *serve* attribute is true. The default value of *storage* attribute will be *true* for single version and *false* for multiple versions to ensure backward compatibility.\n\nCRD generation will be extended to support the following:\n\n* If multiple versions are detected for an API:\n\n    * Ensure only one version is marked as storage version. Assume default value of *storage* to be *false* for this case.\n\n    * Ensure version specific fields such as *OpenAPIValidationSchema, SubResources and AdditionalPrinterColumn* are added per version and omitted from the top level CRD definition.\n\n* In case of single version,\n\n    * Do not use version specific field in CRD spec because users are most likely running with k8s version < 1.13 which doesn’t support version specific specs for *OpenAPIValidationSchema, SubResources and AdditionalPrinterColumn. *This is critical to maintain backward compatibility.\n\n    * Assume default value for storage attribute to be *true* for this case.\n\nThe above two requirements will require CRD generation logic to be divided in two phases. In first phase, parse and store CRD information in an internal structure for all versions and then generate the CRD manifest on the basis of multi-version/single-version scenario.\n\n## Conversion Webhook annotations:\n\nWebhook annotations will be extended to support conversion webhook fields.\n\n```Go\n// ...\n// +kubebuilder:webhook:conversion:....\n// ...\n```\n\nThese annotations would be placed just above the API type definition to associate conversion webhook with an API type.\n\nThe exact syntax for annotation is yet to be defined, but goal is CRD generation tool to be able to extract information from these annotation to populate the `CustomResourceConversion` struct in CRD definition. The CA bits for webhook configuration will be populated by using annotations on the CRD as per the [design](https://docs.google.com/document/d/1ipTvFBRoe7fuDiz27Csm5Zb6rH0z6LJTuKM8xY3jaUg/edit?ts=5c49094e#heading=h.u7ei2s2van5b).\n\n# Kubebuilder CLI:\n\nkubebuilder create api --group g1 --version v2 --Kind k1 [--storage]\n\nFields marked in yellow are proposed new fields to the command and reasoning is stated below.\n\n*  *--storage* flag gives an option to mark a version as storage/hub version.\n\nGenerally users have one controller per group/kind, we will avoid scaffolding code for controller if we detect that a controller already exists for an API group/kind.\n\n# TODO:\n\n## There is more exploration/work is required in the following areas related to API versioning:\n\n* Making it easy to write the conversion function itself.\n\n* Making it easy to generate tests for conversion functions using fuzzer.\n\n* Best practices around rolling out different versions of the API\n\nVersion History\n\n<table>\n  <tr>\n    <td>Version</td>\n    <td>Updated on</td>\n    <td>Description</td>\n  </tr>\n  <tr>\n    <td>Draft</td>\n    <td>01/30/2019\n</td>\n    <td>Initial version</td>\n  </tr>\n  <tr>\n    <td>1.0</td>\n    <td>02/27/2019</td>\n    <td>Updated the design as per POC implementation</td>\n  </tr>\n</table>\n\n\n[version-diaiagram]: assets/version_diagram.png\n"
  },
  {
    "path": "designs/discontinue_usage_of_kube_rbac_proxy.md",
    "content": "| Authors         | Creation Date | Status        | Extra |\n|-----------------|---------------|---------------|-------|\n| @camilamacedo86 | 07/04/2024    | Implementable | -     |\n\n# Discontinue Kube RBAC Proxy in Default Kubebuilder Scaffolding\n\nThis proposal highlights the need to reassess the usage of [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy)\nin the default scaffold due to the evolving k8s infra and community feedback. Key considerations include the transition to a shared infrastructure requiring\nall images to be published on [registry.k8s.io][registry.k8s.io], the deprecation\nof Google Cloud Platform's [Container Registry](https://cloud.google.com/artifact-registry/docs/transition/prepare-gcr-shutdown), and the fact\nthat [kube-rbac-proxy][kube-rbac-proxy] is yet to be part of the Kubernetes ecosystem umbrella.\n\nThe dependency on a potentially discontinuable Google infrastructure,\n**which is out of our control**, paired with the challenges of maintaining,\nbuilding, or promoting [kube-rbac-proxy][kube-rbac-proxy] images,\ncalls for a change.\n\nIn this document is proposed to replace the [kube-rbac-proxy][kube-rbac-proxy] within\n[Network Policies][k8s-doc-networkpolicies] follow-up for potentially enhancements\nto protect the metrics endpoint combined with [cert-manager][cert-manager] and a new\na feature introduced in controller-runtime, see [here][cr-pr].\n\n**For the future (when kube-rbac-proxy be part of the k8s umbrella)**, it is proposed the usage of the\n[Plugins API provided by Kubebuilder](./../docs/book/src/plugins/plugins.md),\nto create an [external plugin](./../docs/book/src/plugins/creating-plugins.md)\nto properly integrate the solution with Kubebuilder and provide a helper to allow users to opt-in as they please them.\n\n## Open Questions\n\n- 1) [Network Policies][k8s-doc-networkpolicies] is implemented by the cluster’s CNI. Are we confident that all the major CNIs in use support the proposed policy?\n\n> Besides [Network Policies][k8s-doc-networkpolicies] being part of the core Kubernetes API, their enforcement relies on the CNI plugin installed in\nthe Kubernetes cluster. While support and implementation details vary among CNIs, the most commonly used ones,\nsuch as Calico, Cilium, WeaveNet, and Canal, offer  support for NetworkPolicies.\n>\n>Also, there was concern in the past because AWS did not support it. However, this changed,\n>as detailed in their announcement: [Amazon VPC CNI now supports Kubernetes Network Policies](https://aws.amazon.com/blogs/containers/amazon-vpc-cni-now-supports-kubernetes-network-policies/).\n>\n>Moreover, under this proposal, users can still disable/enable this option as they please them.\n\n- 2) NetworkPolicy is a simple firewall and does not provide `authn/authz` and encryption.\n\n> Yes, that's correct. NetworkPolicy acts as a basic firewall for pods within a Kubernetes cluster, controlling traffic\n> flow at the IP address or port level. However, it doesn't handle authentication (authn), authorization (authz),\n> or encryption directly like kube-rbac-proxy solution.\n>\n> However, if we can combine the cert-manager and the new feature provided\n> by controller-runtime, we can achieve the same or a superior level of protection\n> without relying on any extra third-party dependency.\n\n- 3) Could not Kubebuilder maintainers use the shared infrastructure to continue building and promoting those images under the new `registry.k8s.io`?\n\n> We tried to do that, see [here](https://github.com/kubernetes/test-infra/blob/master/config/jobs/image-pushing/k8s-staging-kubebuilder.yaml) the recipe implemented.\n> However, it does not work because kube-rbac-proxy is not under the\n> kubernetes umbrella. Moreover, we experimented with the GitHub Repository as an alternative approach, see the [PR](https://github.com/kubernetes-sigs/kubebuilder/pull/3854) but seems\n> that we are not allowed to use it. Nevertheless, neither approach sorts out all motivations and requirements\n> Ideally, Kubebuilder should not be responsible for maintaining and promoting third-party artefacts.\n\n- 4) However, is not Kubebuilder also building and promoting the binaries required to be used within [EnvTest](./../docs/book/src/reference/envtest.md)\nfeature implemented in controller-runtime?\n\n> Yes, but it also will need to change. Controller-runtime maintainers are looking for solutions to\n> build those binaries inside its project since it seems part of its domain. This change is likely\n> to be transparent to the community users.\n\n- 5) Could we not use the Controller-Runtime feature [controller-runtime][cr-pr] which enable secure metrics serving over HTTPS?\n\nYes, after some changes are addressed. After we ask for a hand for reviews from skilled auth maintainers and receive feedback, it appears that this configuration needs to\nalign with best practices. See the [issue](https://github.com/kubernetes-sigs/controller-runtime/issues/2781)\nraised to track this need.\n\n- 6) Could we not make [cert-manager][cert-manager] mandatory?\n\n> No, we can not. One of the goals of Kubebuilder is to make it easier for new\nusers. So, we cannot make mandatory the usage of a third party as cert-manager\nfor users by default and to only quick-start.\n>\n> However, we can make mandatory the usage of\n[cert-manager][cert-manager] for some specific features like use kube-rbac-proxy\nor, as it is today, using webhooks, a more advanced and optional option.\n\n## Summary\n\nStarting with release `3.15.0`, Kubebuilder will no longer scaffold\nnew projects with [kube-rbac-proxy][kube-rbac-proxy].\nExisting users are encouraged to switch to images hosted by the project\non [quay.io](https://quay.io/repository/brancz/kube-rbac-proxy?tab=tags&tag=latest) **OR**\nto adapt their projects to utilize [Network Policies][k8s-doc-networkpolicies], following the updated scaffold guidelines.\n\nFor project updates, users can manually review scaffold changes or utilize the\nprovided [upgrade assistance helper](https://book.kubebuilder.io/reference/rescaffold).\n\nCommunications and guidelines would be provided along with the release.\n\n## Motivation\n\n- **Infrastructure Reliability Concerns**: Kubebuilder’s reliance on Google's infrastructure, which may be discontinued\nat their discretion, poses a risk to image availability and project reliability. [Discussion thread](https://kubernetes.slack.com/archives/CCK68P2Q2/p1711914533693319?thread_ts=1711913605.487359&cid=CCK68P2Q2) and issues: https://github.com/kubernetes/k8s.io/issues/2647 and https://github.com/kubernetes-sigs/kubebuilder/issues/3230\n- **Registry Changes and Image Availability**: The transition from `gcr.io` to [registry.k8s.io][registry.k8s.io] and\nthe [Container Registry][container-registry-dep] deprecation implies that **all** images provided so far by Kubebuilder\n[here][kb-images-repo] will unassailable by **April 22, 2025**. [More info][container-registry-dep] and [slack ETA thread][slack-eta-thread]\n- **Security and Endorsement Concerns**: [kube-rbac-proxy][kube-rbac-proxy] is a process to be part of\nauth-sig for an extended period, however, it is not there yet. The Kubernetes Auth SIG’s review reveals that kube-rbac-proxy\nmust undergo significant updates to secure an official endorsement and to be supported, highlighting pressing concerns.\nYou can check the ongoing process and changes required by looking at the [project issue](https://github.com/brancz/kube-rbac-proxy/issues/238)\n- **Evolving User Requirements and Deprecations**: The anticipated requirement for certificate management, potentially\nnecessitating cert-manager, underlines Kubebuilder's aim to simplify setup and reduce third-party dependencies. [More info, see issue #3524](https://github.com/kubernetes-sigs/kubebuilder/issues/3524)\n- **Aim for a Transparent and Collaborative Infrastructure**: As an open-source project, Kubebuilder strives for\na community-transparent infrastructure that allows broader contributions. This goal aligns with our initiative\nto migrate Kubebuilder CLI release builds from GCP to GitHub Actions and using Go-Releaser see [here](./../build/.goreleaser.yml),\nor promoting infrastructure managed under the k8s-infra umbrella.\n- **Community Feedback**: Some community members preferred its removal from the default scaffolding. [Issue 3482](https://github.com/kubernetes-sigs/kubebuilder/issues/3482)\n- **Enhancing Service Monitor with Proper TLS/Certificate Usage Requested by Community:** [Issue #3657](https://github.com/kubernetes-sigs/kubebuilder/issues/3657). It is achievable with [kube-rbac-proxy][kube-rbac-proxy] OR [Network Policies][k8s-doc-networkpolicies] usage within [cert-manager][cert-manager].\n\n### Goals\n\n- **Maximize Protection for the Metrics Endpoint without relay in third-part(s)**: Aim to provide the highest level of\nprotection achievable for the metrics endpoint without relying on new third-party dependencies or the need to build\nand promote images from other projects.\n- **Avoid Breaking Changes**: Ensure that users who generated projects with previous versions can still use the\nnew version with scaffold changes and adapt their project at their convenience.\n- **Sustainable Project Maintenance**: Ensure all projects scaffolded by Kubebuilder can be\nmaintained and supported by its maintainers.\n- **Independence from Google Cloud Platform**: Move away from reliance on Google Cloud Platform,\nconsidering the potential for unilateral shutdowns.\n- **Kubernetes Umbrella Compliance**: Cease the promotion or endorsement of solutions\nnot yet endorsed by the Kubernetes umbrella organization, mainly when used and shipped with the workload.\n- **Promote Use of External Plugins**: Adhere to Kubebuilder's directive to avoid direct third-party\nintegrations, favouring the support of projects through the Kubebuilder API and [external plugins][external-plugins].\nThis approach empowers users to add or integrate solutions with the Kubebuilder scaffold on their own, ensuring that\nthird-party project maintainers—who are more familiar with their solutions—can maintain and update\ntheir integrations, as implementing it following the best practices to use their project, enhancing the user experience.\nExternal plugins should reside within third-party repository solutions and remain up-to-date as part of those changes,\naligning with their domain of responsibility.\n- **Flexible Network Policy Usage**: Allow users to opt-out of the default-enabled usage of [Network Policies][k8s-doc-networkpolicies]\nif they prefer another solution, plan to deploy their solution with a vendor or use a CNI that does not support NetworkPolicies.\n\n### Non-Goals\n\n- **Replicate kube-rbac-proxy Features or Protection Level**: It is not a goal to provide the same features\nor layer of protection as [kube-rbac-proxy][kube-rbac-proxy]. Since [Network Policies][k8s-doc-networkpolicies]operate differently\nand do not offer the same kind of functionality as [kube-rbac-proxy][kube-rbac-proxy], achieving identical protection levels through\n[Network Policies][k8s-doc-networkpolicies]alone is not feasible.\n\nHowever, incorporating NetworkPolicies, cert-manager, and/or the features introduced\nin the [controller-runtime pull request #2407][cr-pr] we are mainly addressing the security concerns that\nkube-rbac-proxy handles.\n\n## Proposal\n\n### Phase 1: Transition to network policies\n\nThe immediate action outlined in this proposal is the replacement of [kube-rbac-proxy][kube-rbac-proxy]\nwith Kubernetes API NetworkPolicies.\n\n### Phase 2: Add Cert-Manager as an Optional option to be used with metrics\n\nLooking beyond the initial phase, this proposal envisions integrating cert-manager for TLS certificate management\nand exploring synergies with new features in Controller Runtime, as demonstrated in [PR #2407](https://github.com/kubernetes-sigs/controller-runtime/pull/2407).\n\nThese enhancements would introduce encrypted communication for metrics endpoints and potentially incorporate authentication mechanisms,\nsignificantly elevating the security model employed by projects scaffolded by Kubebuilder.\n\n- **cert-manager**: Automates the management and issuance of TLS certificates, facilitating encrypted communication and, when configured with mTLS, adding a layer of authentication.\n  Currently, we leverage cert-manager when webhooks are scaffolded. So, the proposal idea would be to allow users to enable the cert-manager for the metrics such as those provided\n  and required for the webhook feature. However, it MUST be optional. One of the goals of Kubebuilder is to make it easier for new users. Therefore, new users should\n  not need to deal with cert-manager by default or have the need to install it to just a quick start.\n\nThat would mean, in a follow-up to the [current open PR](https://github.com/kubernetes-sigs/kubebuilder/pull/3853) to address the above `phase 1 - Transition to NetworkPolices`,\nwe aim to introduce a configurable Kustomize patch that will enable patching the ServiceMonitor in `config/prometheus/monitor.yaml` and certificates similar to our\nexisting setup for webhooks. This enhancement will ensure more flexible deployment configurations and enhance the security\nfeatures of the service monitoring components.\n\nCurrently, in the `config/default/`, we have implemented patches for cert-manager along with webhooks, as seen in\n`config/default/kustomization.yaml` ([example](https://github.com/kubernetes-sigs/kubebuilder/blob/bd0876b8132ff66da12d8d8a0fdc701fde00f54b/docs/book/src/component-config-tutorial/testdata/project/config/default/kustomization.yaml#L51-L149)).\nThese patches handle annotations for the cert-manager CA injection across various configurations, like\nValidatingWebhookConfiguration, MutatingWebhookConfiguration, and CRDs.\n\nFor the proposed enhancements, we need to integrate similar configurations for the ServiceMonitor.\nThis involves the creation of a patch file named `metrics_https_patch.yaml`, which will include\nconfigurations necessary for enabling HTTPS for the ServiceMonitor.\n\nHere's an example of how this configuration might look:\n\n```sh\n# [METRICS WITH HTTPS] To enable the ServiceMonitor using HTTPS, uncomment the following line\n# Note that for this to work, you also need to ensure that cert-manager is enabled in your project\n- path: metrics_https_patch.yaml\n```\n\nThis patch should apply similar changes as the current webhook patches,\ntargeting necessary updates in the manifest to support HTTPS communication secured by\ncert-manager certificates.\n\nHere is an example of how the `ServiceMonitor` configured to work with cert-manager might look:\n\n```yaml\n# Prometheus Monitor Service (Metrics) with cert-manager\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\n  annotations:\n    cert-manager.io/inject-ca-from: $(NAMESPACE)/controller-manager-certificate\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # We should recommend ensure that TLS verification is not skipped in production\n        insecureSkipVerify: false\n        caFile: /etc/prometheus/secrets/ca.crt # CA certificate injected by cert-manager\n        certFile: /etc/prometheus/secrets/tls.crt # TLS certificate injected by cert-manager\n        keyFile: /etc/prometheus/secrets/tls.key # TLS private key injected by cert-manager\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n```\n\n### Phase 3: When Controller-Runtime feature is enhanced\n\nAfter we have the [issue](https://github.com/kubernetes-sigs/controller-runtime/issues/2781)\naddressed, and we plan to use it to protect the endpoint. See, that would mean ensuring\nthat we are either `handle authentication (authn), authorization (authz)`.\nExamples of its implementation can be found [here](https://github.com/kubernetes-sigs/cluster-api/blob/v1.6.3/util/flags/diagnostics.go#L79-L82).\n\n### Phase 4: When kube-rbac-proxy be accepted under the umbrella\n\nOnce kube-rbac-proxy is included in the Kubernetes umbrella,\nKubebuilder maintainers can support its integration through a [plugin](https://kubebuilder.io/plugins/plugins).\nWe can following up the ongoing process and changes required for the project be accepted\nby looking at the [project issue](https://github.com/brancz/kube-rbac-proxy/issues/238).\n\nThis enables a seamless way to incorporate kube-rbac-proxy into Kubebuilder scaffolds,\nallowing users to run:\n\n```sh\nkubebuilder init|edit --plugins=\"kube-rbac-proxy/v1\"\n```\n\nSo that the plugin could use the [plugin/util](../pkg/plugin/util) lib provide\nto comment (We can add a method like the [UncommentCode](https://github.com/kubernetes-sigs/kubebuilder/blob/72586d386cfbcaecea6321a703d1d7560c521885/pkg/plugin/util/util.go#L102))\nthe patches in the `config/default/kustomization` and disable the default network policy used within\nand [replace the code](https://github.com/kubernetes-sigs/kubebuilder/blob/72586d386cfbcaecea6321a703d1d7560c521885/pkg/plugin/util/util.go#L231)\nin the `main.go` bellow with to not use the controller-runtime\nfeature instead.\n\n```go\nctrlOptions := ctrl.Options{\n    MetricsFilterProvider: filters.WithAuthenticationAndAuthorization,\n    MetricsSecureServing:  true,\n}\n```\n\n### Documentation Updates\n\nEach phase of implementation associated with this proposal must include corresponding\nupdates to the documentation. This is essential to ensure end users understand\nhow to enable, configure, and utilize the options effectively. Documentation updates should be\ncompleted as part of the pull request to introduce code changes.\n\n### Proof of Concept\n\n- **(Phase 1)NetworkPolicies:** https://github.com/kubernetes-sigs/kubebuilder/pull/3853\n- Example of Controller-Runtime new feature to protect Metrics Endpoint: https://github.com/sbueringer/controller-runtime-tests/tree/master/metrics-auth\n\n### Risks and Mitigations\n\n#### Loss of Previously Promoted Images\n\nThe transition to the new shared infrastructure for Kubernetes SIG projects has rendered us unable to automatically build and promote images as before.\nThe process only works for projects under the umbrella.\nHowever, the k8s-infra maintainers could manually transfer these images\nto the new [registry.k8s.io][registry.k8s.io] as a \"contingent approach\".\nSee: [https://explore.ggcr.dev/?repo=gcr.io%2Fk8s-staging-kubebuilder%2Fkube-rbac-proxy](https://explore.ggcr.dev/?repo=registry.k8s.io%2Fkubebuilder%2Fkube-rbac-proxy)\n\nTo continue using kube-rbac-proxy, users must update their projects to reference images\nfrom the new registry. This requires a project update and a new release,\nensuring the image references in the `config/default/manager_auth_proxy_patch.yaml` point\nto a new place.\n\nTherefore, the best approach here for those still interested in using\nkube-rbac-proxy seems to direct them to the images hosted\nat [quay.io](https://quay.io/repository/brancz/kube-rbac-proxy?tab=tags&tag=latest),\nwhich are maintained by the project itself and then,\nwe keep those images in the registry.k8s.io as a \"contingent approach\".\n\nEnsuring that these images will continue to be promoted under any infrastructure available to\nKubebuilder is not reliable or achievable for Kubebuilder maintainers. It is definitely out of our control.\n\n#### Impact of Google Cloud Platform Kubebuilder project\n\nKubebuilder hasn't received any official notice regarding a shutdown of its project there so far, but there's a proactive move to transition away\nfrom Google Cloud Platform services due to factors beyond our control. Open communication with our community is key as\nwe explore alternatives. It's important to note the [Container Registry Deprecation][container-registry-dep] results\nin users no longer able to consume those images from the current location from **early 2025**,\nemphasizing the need to shift away from dependent images as soon as possible and communicate it extensively\nthrough mailing lists and other channels to ensure community awareness and readiness.\n\n## Alternatives\n\n### Replace the current images `gcr.io/kubebuilder/kube-rbac-proxy` with `registry.k8s.io/kubebuilder/kube-rbac-proxy`\n\nThe k8s-infra maintainers assist in ensuring these images will not be lost by:\n- Manually adding them to [gcr.io/k8s-staging-kubebuilder/kube-rbac-proxy](https://explore.ggcr.dev/?repo=gcr.io%2Fk8s-staging-kubebuilder%2Fkube-rbac-proxy) and promoting them via [registry.k8s.io/kubebuilder/kube-rbac-proxy](https://explore.ggcr.dev/?image=registry.k8s.io%2Fkubebuilder%2Fkube-rbac-proxy:v0.16.0).\n\nAn available option would be to communicate to users to:\n- a) Replace their registry from `gcr.io/k8s-staging-kubebuilder/kube-rbac-proxy` to `registry.k8s.io/kubebuilder/kube-rbac-proxy`\n- b) Clearly state in the docs, Kubebuilder scaffolds, and all channels, including email communications, that kube-rbac-proxy is in the process of becoming part of Kubernetes/auth-sig but is not yet there and hence is a \"not supported/secure\" solution\n\n**Cons:**\n- Kubebuilder would still not be fully compliant with its goals since it would be scaffolding a third-party integration instead of properly endorsing and promoting the usage of external-plugin APIs.\n- Kubebuilder would still be promoting a solution not deemed secure/safe according to the review by auth-sig maintainers.\n- We would still need to manually request k8s-infra maintainers to build and promote these images in the new registry manually.\n- Changes in the manager/project solution delivered in the scaffold have a critical impact. For example, in this case, users\nwill need to change **ALL** projects they support and ensure that their users no longer use their previously released versions.\nFollowing this path, when kube-rbac-proxy is accepted under the Kubernetes/auth-sig, they will start to maintain and manage\ntheir own images, which means this path will change again, and Kubebuilder maintainers have no control over ensuring that\nthese images will still be available and promoted for a long period.\n\n### Retain kube-rbac-proxy as an Opt-in Feature and move it to an alpha plugin (Unsupported Feature) AND/OR use the project registry\n\nThis alternative keeps kube-rbac-proxy out of the default scaffolds, offering it as an optional plugin for users who choose\nto integrate it. Clear communication will be crucial to inform users about the implications of using kube-rbac-proxy.\n\n**Cons:**\n\nMainly, all cons added for the above alternative option `Replace the current images gcr.io/kubebuilder/kube-rbac-proxy`\nwith `registry.k8s.io/kubebuilder/kube-rbac-proxy` within the exception that we would make clear that we kubebuilder\nis unable to manage those images and move the current implementation for the alpha plugin\nit would maybe make the process to move it from the Kubebuilder repository to `kube-rbac-proxy` an\neasier process to allow them to work with the external plugin.\n\nHowever, that is a double effort for users and Kubebuilder maintainers to deal with breaking changes\nresulting from achieving the ultimate go. Therefore, it would make more sense\nto encourage using external-plugins API and add this option in their\nrepo once, then create these intermediate steps.\n\n[kube-rbac-proxy]: https://github.com/brancz/kube-rbac-proxy\n[external-plugins]: https://kubebuilder.io/plugins/external-plugins\n[registry.k8s.io]: https://github.com/kubernetes/registry.k8s.io\n[container-registry-dep]: https://cloud.google.com/artifact-registry/docs/transition/prepare-gcr-shutdown\n[kb-images-repo]: https://console.cloud.google.com/gcr/images/kubebuilder/GLOBAL/kube-rbac-proxy\n[slack-eta-thread]: https://kubernetes.slack.com/archives/CCK68P2Q2/p1712622102206909\n[cr-pr]: https://github.com/kubernetes-sigs/controller-runtime/pull/2407\n[k8s-doc-networkpolicies]: https://kubernetes.io/docs/concepts/services-networking/network-policies/\n[cert-manager]:https://cert-manager.io/\n"
  },
  {
    "path": "designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md",
    "content": "| Authors       | Creation Date | Status      | Extra                                                           |\n|---------------|---------------|-------------|-----------------------------------------------------------------|\n| @adirio | Mar 9, 2021  | Implemented | [Plugins doc](https://book.kubebuilder.io/plugins/plugins.html) |\n\n# Extensible CLI and Scaffolding Plugins - Phase 1.5\n\nContinuation of [Extensible CLI and Scaffolding Plugins](./extensible-cli-and-scaffolding-plugins-phase-1.md).\n\n## Goal\n\nThe goal of this phase is to achieve one of the goals proposed for Phase 2: chaining plugins.\nPhase 2 includes several other challenging goals, but being able to chain plugins will be beneficial\nfor third-party developers that are using kubebuilder as a library.\n\nThe other main goal of phase 2, discovering and using external plugins, is out of the scope of this phase,\nand will be tackled when phase 2 is implemented.\n\n## Table of contents\n- [Goal](#goal)\n- [Motivation](#motivation)\n- [Proposal](#proposal)\n- [Implementation](#implementation)\n\n## Motivation\n\nThere are several cases of plugins that want to maintain most of the go plugin functionality and add\ncertain features on top of it, both inside and outside kubebuilder repository:\n- [Addon pattern](../plugins/addon)\n- [Operator SDK](https://github.com/operator-framework/operator-sdk/tree/master/internal/plugins/golang)\n\nThis behavior fits perfectly under Phase 1.5, where plugins could be chained. However, as this feature is\nnot available, the adopted temporal solution is to wrap the base go plugin and perform additional actions\nafter its `Run` method has been executed. This solution faces several issues:\n\n- Wrapper plugins are unable to access the data of the wrapped plugins, as they weren't designed for this\n  purpose, and therefore, most of its internal data is non-exported. An example of this inaccessible data\n  would be the `Resource` objects created inside the `create api` and `create webhook` commands.\n- Wrapper plugins are dependent on their wrapped plugins, and therefore can't be used for other plugins.\n- Under the hood, subcommands implement a second hidden interface: `RunOptions`, which further accentuates\n  these issues.\n\nPlugin chaining solves the aforementioned problems but the current plugin API, and more specifically the\n`Subcommand` interface, does not support plugin chaining.\n\n- The `RunOptions` interface implemented under the hood is not part of the plugin API, and therefore\n  the cli is not able to run post-scaffold logic (implemented in `RunOptions.PostScaffold` method) after\n  all the plugins have scaffolded their part.\n- `Resource`-related commands can't bind flags like `--group`, `--version` or `--kind` in each plugin,\n  it must be created outside the plugins and then injected into them similar to the approach followed\n  currently for `Config` objects.\n\n## Proposal\n\nDesign a Plugin API that combines the current [`Subcommand`](../pkg/plugin/interfaces.go) and\n[`RunOptions`](../pkg/plugins/internal/cmdutil/cmdutil.go) interfaces and enables plugin-chaining.\nThe new `Subcommand` hooks can be split in two different categories:\n- Initialization hooks\n- Execution hooks\n\nInitialization hooks are run during the dynamic creation of the CLI, which means that they are able to\nmodify the CLI, e.g. providing descriptions and examples for subcommands or binding flags.\nExecution hooks are run after the CLI is created, and therefore cannot modify the CLI. On the other hand,\nas they are run during the CLI execution, they have access to user-provided flag values, project configuration,\nthe new API resource or the filesystem abstraction, as opposed to the initialization hooks.\n\nAdditionally, some of these hooks may be optional, in which case a non-implemented hook will be skipped\nwhen it should be called and consider it succeeded. This also allows to create some hooks specific for\na certain subcommand call (e.g.: `Resource`-related hooks for the `edit` subcommand are not needed).\n\nDifferent ordering guarantees can be considered:\n- Hook order guarantee: a hook for a plugin will be called after its previous hooks succeeded.\n- Steps order guarantee: hooks will be called when all plugins have finished the previous hook.\n- Plugin order guarantee: same hook for each plugin will be called in the order specified\n  by the plugin position at the plugin chain.\n\nAll of the hooks will offer plugin order guarantee, as they all modify/update some item so the order\nof plugins is important. Execution hooks need to guarantee step order, as the items that are being modified\nin each step (config, resource, and filesystem) are also needed in the following steps. This is not true for\ninitialization hooks that modify items (metadata and flagset) that are only used in their own methods,\nso they only need to guarantee hook order.\n\nExecution hooks will be able to return an error. A specific error can be returned to specify that\nno further hooks of this plugin should be called, but that the scaffold process should be continued.\nThis enables plugins to exit early, e.g., a plugin that scaffolds some files only for cluster-scoped\nresources can detect if the resource is cluster-scoped at one of the first execution steps, and\ntherefore, use this error to tell the CLI that no further execution step should be called for itself.\n\n### Initialization hooks\n\n#### Update metadata\nThis hook will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g.,\ncommand name) and update the subcommands metadata such as the description or examples.\n\n- Required/optional\n  - [ ] Required\n  - [x] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n#### Bind flags\nThis hook will allow subcommands to define specific flags.\n\n- Required/optional\n  - [ ] Required\n  - [x] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n### Execution methods\n\n#### Inject configuration\nThis hook will be used to inject the `Config` object that the plugin can modify at will.\nThe CLI will create/load/save this configuration object.\n\n- Required/optional\n  - [ ] Required\n  - [x] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n#### Inject resource\nThis hook will be used to inject the `Resource` object created by the CLI.\n\n- Required/optional\n  - [x] Required\n  - [ ] Optional\n- Subcommands\n  - [ ] Init\n  - [ ] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n#### Pre-scaffold\nThis hook will be used to take actions before the main scaffolding is performed, e.g. validations.\n\nNOTE: a filesystem abstraction will be passed to this hook, but it should not be used for scaffolding.\n\n- Required/optional\n  - [ ] Required\n  - [x] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n#### Scaffold\nThis hook will be used to perform the main scaffolding.\n\nNOTE: a filesystem abstraction will be passed to this hook that must be used for scaffolding.\n\n- Required/optional\n  - [x] Required\n  - [ ] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n#### Post-scaffold\nThis hook will be used to take actions after the main scaffolding is performed, e.g. cleanup.\n\nNOTE: a filesystem abstraction will **NOT** be passed to this hook, as post-scaffold task do not require it.\nIn case some post-scaffold task requires a filesystem abstraction, it could be added.\n\nNOTE 2: the project configuration is saved by the CLI before calling this hook, so changes done to the\nconfiguration at this hook will not be persisted.\n\n- Required/optional\n  - [ ] Required\n  - [x] Optional\n- Subcommands\n  - [x] Init\n  - [x] Edit\n  - [x] Create API\n  - [x] Create webhook\n\n### Override plugins for single subcommand calls\n\nDefining plugins at initialization and using them for every command call will solve most of the cases.\nHowever, there are some cases where a plugin may be wanted just for a certain subcommand call. For\nexample, a project with multiple controllers may want to follow the declarative pattern in only one of\ntheir controllers. The other case is also relevant, a project where most of the controllers follow the\ndeclarative pattern may need a single controller not to follow it.\n\nIn order to achieve this, the `--plugins` flag will be allowed in every command call, overriding the\nvalue used in its corresponging project initialization call.\n\n### Plugin chain persistence\n\nCurrently, the project configuration v3 offers two mechanisms for storing plugin-related information.\n\n- A layout field (`string`) that is used for plugin resolution on initialized projects.\n- A plugin field (`map[string]interface{}`) that is used for plugin configuration raw storage.\n\nPlugin resolution uses the `layout` field to resolve plugins. In this phase, it has to store a plugin\nchain and not a single plugin. As this value is stored as a string, comma-separated representation can\nbe used to represent a chain of plugins instead.\n\nNOTE: commas are not allowed in the plugin key.\n\nWhile the `plugin` field may seem like a better fit to store the plugin chain, as it can already\ncontain multiple values, there are several issues with this alternative approach:\n- A map does not provide any order guarantee, and the plugin chain order is relevant.\n- Some plugins do not store plugin-specific configuration information, e.g. the `go`-plugins. So\n  the absence of a plugin key doesn't mean that the plugin is not part of the plugin chain.\n- The desire of running a different set of plugins for a single subcommand call has already been\n  mentioned. Some of these out-of-chain plugins may need to store plugin-specific configuration,\n  so the presence of a plugin doesn't mean that is part of the plugin chain.\n\nThe next project configuration version could consider this new requirements to define the\nnames/types of these two fields.\n\n### Plugin bundle\n\nAs a side-effect of plugin chaining, the user experience may suffer if they need to provide\nseveral plugin keys for the `--plugins` flag. Additionally, this would also mean a user-facing\nimportant breaking change.\n\nIn order to solve this issue, a plugin bundle concept will be introduced. A plugin bundle\nbehaves as a plugin:\n- It has a name: provided at creation.\n- It has a version: provided at creation.\n- It has a list of supported project versions: computed from the common supported project\n  versions of all the plugins in the bundled.\n\nInstead of implementing the optional getter methods that return a subcommand, it offers a way\nto retrieve the list of bundled plugins. This process will be done after plugin resolution.\n\nThis way, CLIs will be able to define bundles, which will be used in the user-facing API and\nthe plugin resolution process, but later they will be treated as separate plugins offering\nthe maintainability and separation of concerns advantages that smaller plugins have in\ncomparison with bigger monolithic plugins.\n\n## Implementation\n\nThe following types are used as input/output values of the described hooks:\n```go\n// CLIMetadata is the runtime meta-data of the CLI\ntype CLIMetadata struct {\n\t// CommandName is the root command name.\n\tCommandName string\n}\n\n// SubcommandMetadata is the runtime meta-data for a subcommand\ntype SubcommandMetadata struct {\n\t// Description is a description of what this subcommand does. It is used to display help.\n\tDescription string\n\t// Examples are one or more examples of the command-line usage of this subcommand. It is used to display help.\n\tExamples string\n}\n\ntype ExitError struct {\n\tPlugin string\n\tReason string\n}\n\nfunc (e ExitError) Error() string {\n\treturn fmt.Sprintf(\"plugin %s exit early: %s\", e.Plugin, e.Reason)\n}\n```\n\nThe described hooks are implemented through the use of the following interfaces.\n```go\ntype RequiresCLIMetadata interface {\n\tInjectCLIMetadata(CLIMetadata)\n}\n\ntype UpdatesSubcommandMetadata interface {\n\tUpdateSubcommandMetadata(*SubcommandMetadata)\n}\n\ntype HasFlags interface {\n\tBindFlags(*pflag.FlagSet)\n}\n\ntype RequiresConfig interface {\n\tInjectConfig(config.Config) error\n}\n\ntype RequiresResource interface {\n\tInjectResource(*resource.Resource) error\n}\n\ntype HasPreScaffold interface {\n\tPreScaffold(machinery.Filesystem) error\n}\n\ntype Scaffolder interface {\n\tScaffold(machinery.Filesystem) error\n}\n\ntype HasPostScaffold interface {\n\tPostScaffold() error\n}\n```\n\nAdditional interfaces define the required method for each type of plugin:\n```go\n// InitSubcommand is the specific interface for subcommands returned by init plugins.\ntype InitSubcommand interface {\n\tScaffolder\n}\n\n// EditSubcommand is the specific interface for subcommands returned by edit plugins.\ntype EditSubcommand interface {\n\tScaffolder\n}\n\n// CreateAPISubcommand is the specific interface for subcommands returned by create API plugins.\ntype CreateAPISubcommand interface {\n\tRequiresResource\n\tScaffolder\n}\n\n// CreateWebhookSubcommand is the specific interface for subcommands returned by create webhook plugins.\ntype CreateWebhookSubcommand interface {\n\tRequiresResource\n\tScaffolder\n}\n```\n\nAn additional interface defines the bundle method to return the wrapped plugins:\n```go\ntype Bundle interface {\n\tPlugin\n\tPlugins() []Plugin\n}\n```\n"
  },
  {
    "path": "designs/extensible-cli-and-scaffolding-plugins-phase-1.md",
    "content": "\n| Authors       | Creation Date | Status      | Extra                                                           |\n|---------------|---------------|-------------|-----------------------------------------------------------------|\n| @estroz,@joelanford | Dec 10, 2019  | Implemented | [Plugins doc](https://book.kubebuilder.io/plugins/plugins.html) |\n\n# Extensible CLI and Scaffolding Plugins\n\n## Overview\n\nI would like for Kubebuilder to become more extensible, such that it could be imported and used as a library in other projects. Specifically, I'm looking for a way to use Kubebuilder's existing CLI and scaffolding for Go projects, but to also be able to augment the Kubebuilder project structure with other custom project types so that I can support the Kubebuilder workflow with non-Go operators (e.g. operator-sdk's Ansible and Helm-based operators).\n\nThe idea is for Kubebuilder to define one or more plugin interfaces that can be used to drive what the `init`, `create api` and `create webhooks` subcommands do and to add a new `cli` package that other projects can use to integrate out-of-tree plugins with the Kubebuilder CLI in their own projects.\n\n## Related issues and PRs\n\n* [#1148](https://github.com/kubernetes-sigs/kubebuilder/pull/1148)\n* [#1171](https://github.com/kubernetes-sigs/kubebuilder/pull/1171)\n* Possibly [#1218](https://github.com/kubernetes-sigs/kubebuilder/issues/1218)\n\n## Prototype implementation\n\nBarebones plugin refactor: https://github.com/joelanford/kubebuilder-exp\nKubebuilder feature branch: https://github.com/kubernetes-sigs/kubebuilder/tree/feature/plugins-part-2-electric-boogaloo\n\n## Plugin interfaces\n\n### Required\n\nEach plugin would minimally be required to implement the `Plugin` interface.\n\n```go\ntype Plugin interface {\n    // Version returns the plugin's semantic version, ex. \"v1.2.3\".\n    //\n    // Note: this version is different from config version.\n    Version() string\n    // Name returns a DNS1123 label string defining the plugin type.\n    // For example, Kubebuilder's main plugin would return \"go\".\n    //\n    // Plugin names can be fully-qualified, and non-fully-qualified names are\n    // prepended to \".kubebuilder.io\" to prevent conflicts.\n    Name() string\n    // SupportedProjectVersions lists all project configuration versions this\n    // plugin supports, ex. []string{\"2\", \"3\"}. The returned slice cannot be empty.\n    SupportedProjectVersions() []string\n}\n```\n\n#### Plugin naming\n\nPlugin names (returned by `Name()`) must be DNS1123 labels. The returned name\nmay be fully qualified (fq), ex. `go.kubebuilder.io`, or not but internally will\nalways be fq by either appending `.kubebuilder.io` to the name or using an\nexisting qualifier defined by the plugin. FQ names prevent conflicts between\nplugin names; the plugin runner will ask the user to add a name qualifier to\na conflicting plugin.\n\n### Optional\n\nNext, a plugin could optionally implement further interfaces to declare its support for specific Kubebuilder subcommands. For example:\n* `InitPlugin` - to initialize new projects\n* `CreateAPIPlugin` - to create APIs (and possibly controllers) for existing projects\n* `CreateWebhookPlugin` - to create webhooks for existing projects\n\nEach of these interfaces would follow the same pattern (see the `InitPlugin` interface example below).\n\n```go\ntype InitPluginGetter interface {\n    Plugin\n    // GetInitPlugin returns the underlying InitPlugin interface.\n    GetInitPlugin() InitPlugin\n}\n\ntype InitPlugin interface {\n    GenericSubcommand\n}\n```\n\nEach specialized plugin interface can leverage a generic subcommand interface, which prevents duplication of methods while permitting type checking and interface flexibility. A plugin context can be used to preserve default help text in case a plugin does not implement its own.\n\n```go\ntype GenericSubcommand interface {\n    // UpdateContext updates a PluginContext with command-specific help text, like description and examples.\n    // Can be a no-op if default help text is desired.\n    UpdateContext(*PluginContext)\n    // BindFlags binds the plugin's flags to the CLI. This allows each plugin to define its own\n    // command line flags for the kubebuilder subcommand.\n    BindFlags(*pflag.FlagSet)\n    // Run runs the subcommand.\n    Run() error\n    // InjectConfig passes a config to a plugin. The plugin may modify the\n    // config. Initializing, loading, and saving the config is managed by the\n    // cli package.\n    InjectConfig(*config.Config)\n}\n\ntype PluginContext struct {\n    // Description is a description of what this subcommand does. It is used to display help.\n    Description string\n    // Examples are one or more examples of the command-line usage\n    // of this plugin's project subcommand support. It is used to display help.\n    Examples string\n}\n```\n\n#### Deprecated Plugins\n\nTo generically support deprecated project versions, we could also add a `Deprecated` interface that the CLI could use to decide when to print deprecation warnings:\n\n```go\n// Deprecated is an interface that, if implemented, informs the CLI\n// that the plugin is deprecated.  The CLI uses this to print deprecation\n// warnings when the plugin is in use.\ntype Deprecated interface {\n    // DeprecationWarning returns a deprecation message that callers\n    // can use to warn users of deprecations\n    DeprecationWarning() string\n}\n```\n\n## Configuration\n\n### Config version `3-alpha`\n\nAny changes that break `PROJECT` file backwards-compatibility require a version\nbump. This new version will be `3-alpha`, which will eventually be bumped to\n`3` once the below config changes have stabilized.\n\n### Project file plugin `layout`\n\nThe `PROJECT` file will specify what base plugin generated the project under\na `layout` key. `layout` will have the format: `Plugin.Name() + \"/\" + Plugin.Version()`.\n`version` and `layout` have versions with different meanings: `version` is the\nproject config version, while `layout`'s version is the plugin semantic version.\nThe value in `version` will determine that in `layout` by a plugin's supported\nproject versions (via `SupportedProjectVersions()`).\n\nExample `PROJECT` file:\n\n```yaml\nversion: \"3-alpha\"\nlayout: go/v1.0.0\ndomain: testproject.org\nrepo: github.com/test-inc/testproject\nresources:\n- group: crew\n  kind: Captain\n  version: v1\n```\n\n## CLI\n\nTo make the above plugin system extensible and usable by other projects, we could add a new CLI package that Kubebuilder (and other projects) could use as their entrypoint.\n\nExample Kubebuilder main.go:\n\n```go\nfunc main() {\n\tc, err := cli.New(\n\t\tcli.WithPlugins(\n\t\t\t&golangv1.Plugin{},\n\t\t\t&golangv2.Plugin{},\n\t\t),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := c.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\n\nExample Operator SDK main.go:\n\n```go\nfunc main() {\n\tc, err := cli.New(\n\t\tcli.WithCommandName(\"operator-sdk\"),\n\t\tcli.WithDefaultProjectVersion(\"2\"),\n\t\tcli.WithExtraCommands(newCustomCobraCmd()),\n\t\tcli.WithPlugins(\n\t\t\t&golangv1.Plugin{},\n\t\t\t&golangv2.Plugin{},\n\t\t\t&helmv1.Plugin{},\n\t\t\t&ansiblev1.Plugin{},\n\t\t),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := c.Run(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\n\n## Comments & Questions\n\n### Cobra Commands\n\n**RESOLUTION:** `cobra` will be used directly in Phase 1 since it is a widely used, feature-rich CLI package. This, however unlikely, may change in future phases.\n\nAs discussed earlier as part of [#1148](https://github.com/kubernetes-sigs/kubebuilder/pull/1148), one goal is to eliminate the use of `cobra.Command` in the exported API of Kubebuilder since that is considered an internal implementation detail.\n\nHowever, at some point, projects that make use of this extensibility will likely want to integrate their own subcommands. In this proposal, `cli.WithExtraCommands()` _DOES_ expose `cobra.Command` to allow callers to pass their own subcommands to the CLI.\n\nIn [#1148](https://github.com/kubernetes-sigs/kubebuilder/pull/1148), callers would use Kubebuilder's cobra commands to build their CLI. Here, control of the CLI is retained by Kubebuilder, and callers pass their subcommands to Kubebuilder. This has several benefits:\n1. Kubebuilder's CLI subcommands are never exposed except via the explicit plugin interface. This allows the Kubebuilder project to re-implement its subcommand internals without worrying about backwards compatibility of consumers of Kubebuilder's CLI.\n2. If desired, Kubebuilder could ensure that extra subcommands do not overwrite/reuse the existing Kubebuilder subcommand names. For example, only Kubebuilder gets to define the `init` subcommand\n3. The overall binary's help handling is self-contained in Kubebuilder's CLI. Callers don't have to figure out how to have a cohesive help output between the Kubebuilder CLI and their own custom subcommands.\n\nWith all of that said, even this exposure of `cobra.Command` could be problematic. If Kubebuilder decides in the future to transition to a different CLI framework (or to roll its own) it has to either continue maintaining support for these extra cobra commands passed into it, or it was to break the CLI API.\n\nAre there other ideas for how to handle the following requirements?\n* Eliminate use of cobra in CLI interface\n* Allow other projects to have custom subcommands\n* Support cohesive help output\n\n### Other\n1. ~Should the `InitPlugin` interface methods be required of all plugins?~ No\n2. ~Any other approaches or ideas?~\n3. ~Anything I didn't cover that could use more explanation?~\n"
  },
  {
    "path": "designs/extensible-cli-and-scaffolding-plugins-phase-2.md",
    "content": "| Authors       | Creation Date | Status      | Extra                                                           |\n|---------------|---------------|-------------|-----------------------------------------------------------------|\n| @rashmigottipati | Mar 9, 2021  | partial implemented | [Plugins doc](https://book.kubebuilder.io/plugins/plugins.html) |\n\n# Extensible CLI and Scaffolding Plugins - Phase 2\n\n## Overview\n\nPlugin [Phase 1.5](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md) was designed and implemented to allow chaining of plugins. The purpose of Phase 2 plugins is to discover and use external plugins, also referred to as out-of-tree plugins (which can be implemented in any language). Phase 2 achieves both chaining and discovery of external plugins/source code not compiled with the `kubebuilder` CLI binary. By achieving this goal, we could (for example) externalize the optional [declarative plugin](https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/plugins/golang/declarative/v1) which means that the CLI would still be able to use it, however, its source code would no longer be required to be inside of the Kubebuilder repository.\n\n### Related issues and PRs\n\n* [Feature Request: Plugins Phase 2](https://github.com/kubernetes-sigs/kubebuilder/issues/1378)\n* [Extensible CLI and Scaffolding Plugins - Phase 1.5](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md)\n* [Phase 1.5 Implementation PR](https://github.com/kubernetes-sigs/kubebuilder/pull/2060)\n* [Plugin Resolution Enhancement Proposal](https://github.com/kubernetes-sigs/kubebuilder/pull/1942)\n\n### Prototype implementation\n\n[POC](https://github.com/rashmigottipati/POC-Phase2-Plugins) - Invoke an external python program that simulates a plugin from a go main and pass messages from `kubebuilder` to the plugin and vice-versa using `stdin/stdout/stderr`.\n\n### User Stories\n\n* As a plugin developer, I would like to be able to provide external plugins path for the CLI to perform the scaffolds, so that I could take advantage of external initiatives which are implemented using Kubebuilder as a lib and following its standards but are not shipped with its CLI binaries.\n\n* As a Kubebuilder maintainer, I would like to support external plugins not maintained by the core project.\n  * For example, once the Phase 2 plugin implementation is completed, some internal plugins can be re-implemented as external plugins removing the necessity to build those plugins in the `kubebuilder` binary.\n\n### Goals\n\n* `kubebuilder` is able to discover plugin binaries and run those plugins using the CLI.\n\n* Kubebuilder can use the external plugins as well as its own internal ones to do scaffolding.\n\n* `kubebuilder` should be able to show plugin specific information via the `--help` flag.\n\n* Support for standard streams i.e. `stdin/stdout/stderr` as the only IPC method between `kubebuilder` and plugins.\n\n* Kubebuilder library consumers can support chaining and discovery of out-of-tree plugins.\n\n### Non-Goals\n\n* Addition of new arbitrary subcommands other than the subcommands that we already support i.e `init`, `create api`, and `create webhook`.\n\n* Discovering plugin binaries that are not locally present on the machine (i.e. binary exists in a remote repository).\n\n* Providing other options (other than standard streams such as `stdin/stdout/stderr`) for inter-process communication between `kubebuilder` and external plugins.\n  * Other IPC methods may be allowed in the future, although EPs are required for those methods.\n\n### Examples\n\n* `kubebuilder create api --plugins=myexternalplugin/v1`\n  * should scaffold files using the external plugin as defined in its implementation of the `create api` method.\n\n* `kubebuilder create api --plugins=myexternalplugin/v1,myotherexternalplugin/v2`\n  * should scaffold files using the external plugin as defined in their implementation of the `create api` method (by respecting the plugin chaining order, i.e. in the order of `create api` of v1 and then `create api` of v2 as specified in the layout field in the configuration).\n\n* `kubebuilder create api --plugins=myexternalplugin/v1 --help`\n  * should display help information of the plugin which is not shipped in the binary (myexternalplugin/v1 is present outside of the `kubebuilder` binary).\n\n* `kubebuilder create api --plugins=go/v3,myexternalplugin/v2`\n  * should create files using the `go/v3` plugin, then pass those files to `myexternalplugin/v2` as defined in its implementation of the `create api` method by respecting the plugin chaining order.\n\n## Proposal\n\n### Discovery of plugin binaries\n\nThe method [kustomize](https://kubectl.docs.kubernetes.io/guides/extending_kustomize/) uses to discover plugins, by following a GVK path scheme, is the most natural for this use case since plugins must have a group-like name and version.\n\nEvery plugin gets its own directory constructed using the plugin name and plugin version for the executable to be placed in and `kubebuilder` will search for a plugin binary with the name of the plugin in the `${name}/${version}` directory of the plugin. This information (plugin name and plugin version) is obtained by `kubebuilder` via the value passed to the `--plugins` CLI flag. Once `kubebuilder` successfully locates the plugin, it will run the plugin using the CLI.\n\nEvery plugin gets its own directory as below.\n\nOn Linux:\n\n```shell\n    $XDG_CONFIG_HOME/kubebuilder/plugins/${name}/${version}\n```\n\nThe default value of XDG_CONFIG_HOME is `$HOME/.config`.\n\nOn OSX:\n\n```shell\n    ~/Library/Application Support/kubebuilder/plugins/${name}/${version}\n```\n\nBased on the above directory scheme, let's say that if the value passed to the `--plugins` CLI flag is `myexternalplugin/v1`:\n\n* On Linux:\n  * `kubebuilder` will search for the `myexternalplugin` binary in `$XDG_CONFIG_HOME/kubebuilder/plugins/myexternalplugin/v1`, where the base of this path in is the binary name.\n* On OSX:\n  * Kubebuilder will search for the `myexternalplugin` binary in `$HOME/Library/Application Support/kubebuilder/plugins/myexternalplugin/v1`.\n\nNote: If the name is ambiguous, then the qualified name `myexternalplugin.my.domain` would be used, so the path would be `$XDG_CONFIG_HOME/kubebuilder/plugins/my/domain/myexternalplugin/v1` on Linux and `$HOME/Library/Application Support/kubebuilder/plugins/my/domain/myexternalplugin/v1` on OSX.\n\n* Pros\n  * `kustomize` which is popular and robust tool, follows this approach in which `apiVersion` and `kind` fields are used to locate the plugin.\n\n  * This approach enforces naming constraints as the permitted character set must be directory name-compatible following naming rules for both Linux and OSX systems.\n\n  * The one-plugin-per-directory requirement eases creation of a plugin bundle for sharing.\n\n### What Plugin system should we use\n\nI 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.\n\nWe 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):\n\n* 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.\n* 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.\n\nIn 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.\n\n### Project configuration\n\nCurrently, the project configuration has two fields to store plugin specific information.\n\n* `Layout` field (of type []string) is used for plugin chain resolution on initialized projects. This will be the default if no plugins are specified for a subcommand.\n* `Plugins` field (of type map[string]interface{}) is used for option plugin configuration that stores configuration information of any plugin.\n\n* So, where should external plugins be defined in the configuration?\n\n  * I propose that the external plugin should get encoded in the project configuration as a part of the `layout` field.\n    * For example, external plugin `myexternalplugin/v2` can be specified through the `--plugins` flag for every subcommand and also be defined in the project configuration in the `layout` field for plugin resolution.\n\nExample `PROJECT` file:\n\n```yaml\nversion: \"3\"\ndomain: testproject.org\nlayout:\n- go.kubebuilder.io/v3\n- myexternalplugin/v2\nplugins:\n  myexternalplugin/v2:\n    resources:\n    - domain: testproject.org\n      group: crew\n      kind: Captain\n      version: v2\n  declarative.go.kubebuilder.io/v1:\n    resources:\n    - domain: testproject.org\n      group: crew\n      kind: FirstMate\n      version: v1\nrepo: github.com/test-inc/testproject\nresources:\n- group: crew\n  kind: Captain\n  version: v1\n```\n\n### Communication between `kubebuilder` and external plugins\n\n* Why do we need communication between `kubebuilder` and external plugins?\n\n  * The in-tree plugins do not need any inter-process communication as they are the same process, and hence, direct calls are made to the respective functions (also referred as hooks) based on the supported subcommands for an in-tree plugin. As Phase 2 plugins is tackling out-of-tree or external plugins, there's a need for inter-process communication between `kubebuilder` and the external plugin as they are two separate processes/binaries. `kubebuilder` needs to communicate the subcommand that the external plugin should run, and all the arguments received in the CLI request by the user. These arguments contain flags which will have to be directly passed to all plugins in the chain. Additionally, it's important to have context of all the files that were scaffolded until that point especially if there is more than one external plugin in the chain. `kubebuilder` attaches that information in the request, along with the command and arguments. For the external plugin, it would need to communicate the subcommand it ran and the updated file contents information that the external plugin scaffolded to `kubebuilder`. The external plugin would also need to provide its help text if requested by `kubebuilder`. As discussed earlier, standard streams seems to be a desirable IPC method of communication for the use-cases that Phase 2 is trying to solve that involves discovery and chaining of external plugins.\n\n* How does `kubebuilder` communicate to external plugins?\n\n  * Standard streams have three I/O connections: standard input (`stdin`), standard output (`stdout`) and standard error (`stderr`) and they work well with chaining applications, meaning that output stream of one program can be redirected to the input stream of another.\n  * Let's say there are two external plugins in the plugin chain. Below is the sequence of how `kubebuilder` communicates to the plugins `myfirstexternalplugin/v1` and `mysecondexternalplugin/v1`.\n\n![Kubebuilder to external plugins sequence diagram](https://github.com/rashmigottipati/POC-Phase2-Plugins/blob/main/docs/externalplugins-sequence-diagram.png)\n\n* What should be passed between `kubebuilder` and an external plugin?\n\nMessage 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.\n\nThe following scenarios show what `kubebuilder` will send/receive to the external plugin:\n\n* `kubebuilder` to external plugin:\n  * `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`.\n\n* External plugin to `kubebuilder`:\n  * 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`.\n\n* Note: If `--help` flag is being passed from `kubebuilder` to the external plugin through `PluginRequest`, the plugin attaches its help text information in the `Metadata` field of the `PluginResponse`. Both `PluginRequest` and `PluginResponse` also contain `APIVersion` field to have compatible versioned schemas.\n\n* Handling plugin failures across the chain:\n\n  * If any plugin in the chain fails, the plugin reports errors back through `PluginResponse` to `kubebuilder` and plugin chain execution will be halted, as one plugin may be dependent on the success of another. All the files that were scaffolded already until that point will not be written to disk to prevent a half committed state.\n\n## Implementation Details/Notes/Constraints\n\n`PluginRequest` holds all the information `kubebuilder` receives from the CLI and the plugins that were executed before it and the `PluginRequest` will be marshaled into a JSON and sent over `stdin` to the external plugin. `PluginResponse` is what the plugin constructs with the updated universe and sent back to `kubebuilder`. The following structs would be defined on the Kubebuilder side.\n\n```go\n// PluginRequest contains all information kubebuilder received from the CLI\n// and plugins executed before it.\ntype PluginRequest struct {\n  // Command contains the command to be executed by the plugin such as init, create api, etc.\n  Command       string              `json:\"command\"`\n\n  // APIVersion defines the versioned schema of the PluginRequest that is encoded and sent from Kubebuilder to plugin.\n  // Initially, this will be marked as alpha (v1alpha1).\n  APIVersion    string              `json:\"apiVersion\"`\n\n  // Args holds the plugin specific arguments that are received from the CLI which are to be passed down to the plugin.\n  Args          []string            `json:\"args\"`\n\n  // Universe represents the modified file contents that gets updated over a series of plugin runs\n  // across the plugin chain. Initially, it starts out as empty.\n  Universe      map[string]string   `json:\"universe\"`\n}\n\n// PluginResponse is returned to kubebuilder by the plugin and contains all files\n// written by the plugin following a certain command.\ntype PluginResponse struct {\n  // Command holds the command that gets executed by the plugin such as init, create api, etc.\n  Command       string                   `json:\"command\"`\n\n  // Metadata contains the plugin specific help text that the plugin returns to Kubebuilder when it receives\n  // `--help` flag from Kubebuilder.\n  Metadata plugin.SubcommandMetadata `json:\"metadata\"`\n\n  // APIVersion defines the versioned schema of the PluginResponse that will be written back to kubebuilder.\n  // Initially, this will be marked as alpha (v1alpha1).\n  APIVersion    string                   `json:\"apiVersion\"`\n\n  // Universe in the PluginResponse represents the updated file contents that was written by the plugin.\n  Universe      map[string]string        `json:\"universe\"`\n\n  // Error is a boolean type that indicates whether there were any errors due to plugin failures.\n  Error         bool                     `json:\"error,omitempty\"`\n\n  // ErrorMsg holds the specific error message of plugin failures.\n  ErrorMsg      string                   `json:\"error_msg,omitempty\"`\n}\n```\n\nThe following function handles construction of the `PluginRequest` based on the information `kubebuilder` receives from the CLI and the request is marshaled into JSON. The command to run the external plugin by providing the plugin path will be invoked and `kubebuilder` will send the marshaled `PluginRequest` JSON to the plugin over `stdin`.\n\n```go\nfunc (p *ExternalPlugin) runExternalProgram(req PluginRequest) (res PluginResponse, err error) {\n  pluginReq, err := json.Marshal(req)\n  if err != nil {\n    return res, err\n  }\n\n  cmd := exec.Command(p.Path)\n  cmd.Dir = p.DirContext\n  cmd.Stdin = bytes.NewBuffer(pluginReq)\n  cmd.Stderr = os.Stderr\n\n  out, err := cmd.Output()\n  if err != nil {\n    fmt.Fprint(os.Stdout, string(out))\n    return res, err\n  }\n\n  if json.Unmarshal(out, &res); err != nil {\n    return res, err\n  }\n  return res, nil\n}\n```\n\nOn the plugin side, the request JSON will be decoded and depending on what the `Command` in the `PluginRequest` is, the corresponding function to handle `init` or `create api` will be invoked thereby modifying the universe by writing the updated files to it. After `init` or `create api` functions execute successfully, the plugin will write back `PluginResponse` with updated universe and errors (if any) in JSON format through `stdout` to `kubebuilder`. `PluginResponse` also contains error fields `Error` and `ErrorMsg` that the plugin can utilize to add error context if any errors occur.\n`kubebuilder` receives the command output and decodes into `PluginResponse` struct. This is how message passing will occur between `kubebuilder` and the external plugin. Refer to [POC](https://github.com/rashmigottipati/POC-Phase2-Plugins) for specifics.\n\n### Simple Example\n\n```shell\nkubebuilder init --plugins=myexternalplugin/v1 --domain example.com\n```\n\nWhat happens when the above is invoked?\n\n![Kubebuilder to external plugins](https://github.com/rashmigottipati/POC-Phase2-Plugins/blob/main/docs/externalplugins-sequence-diagram-2.png)\n\n* `kubebuilder` discovers `myexternalplugin/v1` plugin binary and runs the plugin from the discovered path.\n\n* Send `PluginRequest` as a JSON over `stdin` to `myexternalplugin` plugin.\n\n`PluginRequest JSON`:\n\n```JSON\n{\n  \"command\":\"init\",\n  \"args\":[\"--domain\",\"example.com\"],\n  \"universe\":{}\n}\n```\n\n* `myexternalplugin` plugin parses the `PluginRequest` and based on the `Command` specified in the request i.e `init`, performs the necessary scaffolding.\n\n* `myexternalplugin` plugin constructs `PluginResponse` with modified `Universe` that contains the updated file contents and errors if any.\n\n* Plugin writes `PluginResponse` to stdout in a JSON format back to `kubebuilder`.\n\n* `kubebuilder` receives the command output containing the `PluginResponse` JSON which will be decoded into the `PluginResponse` struct.\n\n* `kubebuilder` writes the files in the universe to disk.\n\n`PluginResponse JSON`:\n\n```JSON\n{\n  \"command\": \"init\",\n  \"universe\": {\n    \"LICENSE\": \"Apache 2.0 License\\n\",\n    \"main.py\": \"...\"\n  }\n}\n\n```\n\n## Alternatives\n\n### Plugin discovery\n\n#### User specified file paths\n\nA 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.\n\n* 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.\n\n* Pros\n  * This provides flexibility for the user to specify the file paths that the plugin would be placed in and `kubebuilder` could discover the binaries in those user specified file paths.\n\n  * No constraints on plugin binary naming or directory placements from the Kubebuilder side.\n\n  * Provides a default value for the plugin directory in case user wants to use that to drop their plugins.\n\n#### Prefixed plugin executable names in $PATH\n\nAnother 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.\n\n* Pros\n  * `kubectl` and `git` follow the same approach for discovering plugins, so there's prior art.\n\n  * 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.\n\n* Cons\n  * 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.\n\n  * These plugin binaries cannot be run in a standalone manner outside of Kubebuilder, so may not be very ideal to add them to the PATH var.\n\n## Open questions\n\n* Do we want to support the addition of new arbitrary subcommands other than the subcommands (init, create api, create webhook) that we already support?\n  * Not for the EP or initial implementation, but can revisit later.\n\n* Do we need to discover flags by calling the plugin binary or should we have users define them in the project configuration?\n  * Flags will be passed directly to the external plugins as a string. Flag parse errors will be passed back via `PluginResponse`.\n\n* What alternatives to stdin/stdout exist and why shouldn't we use them?\n  * Other alternatives exist such as named pipe and sockets, but stdin/stdout seems to be more suitable for our needs.\n\n* What happens when two plugins bind the same flag name? Will there be any conflicts?\n  * As mentioned in the implementation details section, flags are passed directly as a string to plugins and the same string will be passed to each plugin in the chain, so all plugins get the same flag set. Errors should not be returned if an unrecognized flag is parsed.\n\n* How should we handle environment variables?\n  * We would pass the entire CLI environment to the plugin to permit simple external plugin configuration without jumping through hoops.\n\n* Should the API version be a part of the plugin request spec?\n  * It would be nice to encode APIVersion for `PluginRequest` and `PluginResponse` so the initial schemas can be marked as `v1alpha1`.\n"
  },
  {
    "path": "designs/helm-chart-autogenerate-plugin.md",
    "content": "| Authors                                | Creation Date | Status       | Extra |\n|----------------------------------------|---------------|--------------|-------|\n| @dashanji,@camilamacedo86,@LCaparelli  | Sep, 2023     | Implemented  | -     |\n\n# New Plugin to allow project distribution via helm charts\n\nThis proposal aims to introduce an optional mechanism that allows users\nto generate a Helm Chart from their Kubebuilder-scaffolded project. This will\nenable them to effectively package and distribute their solutions.\n\nTo achieve this goal, we are proposing a new native Kubebuilder\n[plugin](https://book.kubebuilder.io/plugins/plugins) (i.e., `helm-chart/v1-alpha`)\nwhich will provide the necessary scaffolds. The plugin will function similarly\nto the existing [Grafana Plugin](https://book.kubebuilder.io/plugins/grafana-v1-alpha), generating or regenerating HelmChart files using the init and edit\nsub-commands (i.e., `kubebuilder init|edit --plugins helm-chart/v1-alpha`).\n\nAn alternative solution could be to implement an alpha command,\nsimilar to the [helper provided to upgrade projects](https://book.kubebuilder.io/reference/rescaffold) that would\nprovide the HelmChart under the `dist`directory, similar to what\nis done by [helmify](https://github.com/arttor/helmify).\n\n## Example of Usage\n\n**To enable the helm-chart generation when a project is initialized**\n\n> kubebuilder init --plugins=`go/v4,helm/v1-alpha`\n\n**To enable the helm-chart generation after the project be scaffolded**\n\n> kubebuilder edit --plugins=`helm/v1-alpha`\n\n> Note that the HelmChart should be scaffold under the `dist/` directory in both scenarios:\n> ```shell\n> example-project/\n>   dist/\n>     chart/\n> ```\n\n\n**To sync the HelmChart with the latest changes and add the manifests generated**\n\n> kubebuilder edit --plugins=`helm/v1-alpha`\n\nThe above command will be responsible for ensuring that the Helm Chart is properly updated\nwith the latest changes in the project, including the files generated by\ncontroller-gen when users run make manifests.\n\n## Open Questions\n\n### 1) How to manage and scaffold the CRDs for the HelmChart?\n\nAccording to [Helm Best Practices for Custom Resource Definitions](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you),\nthere are two main methods for handling CRDs:\n\n- **Method 1:Let Helm Do It For You:**  Place CRDs in the `crds/` directory. Helm installs these CRDs during the initial\n  install but does not manage upgrades or deletions.\n- **Method 2:Separate Charts:**  Place the CRD definition in one chart\n  and the resources using the CRD in another chart.\n  This method requires separate installations for each chart.\n\n**Raised Considerations and Concerns**\n- **Use Helm crd directory** The upgraded chart versions will silently ignore CRDs even if they differ from the installed versions. This could lead to surprising and unexpected behavior. Therefore, Kubebuilder should not encourage or promote this approach.\n- **Templates Folder**: Moving CRDs to the `templates` folder facilitates upgrades but uninstalls CRDs when the operator is uninstalled. However, it allows users easier manage the CRDs and install them on upgrades. It is a common approach adopted by maintainers but is not considered a good practice by Helm itself.\n- **Separate Helm Chart for CRDs:** This approach allows control over both CRD and operator versions without deleting CRDs when the operator chart is deleted. Also, follows the HelmChart best practices. Another problem with this approach is to ensure that the CRDs will be applied before the CRs since both will be under the template directory.\n- **When Webhooks are used:** If a CRD specifies, for example, a conversion webhook, the \"API chart\" needs to contain the CRDs and the webhook `service/workload`.\n  It would also make sense to include `validating/mutating` webhooks, requiring the scaffolding of separate main modules and image builds for\n  webhooks and controllers which does not shows to be compatible with Kubebuilder Golang scaffold.\n\n**Proposed Solution**\n\nFollow the same approach adopted by [Cert-Manager](https://cert-manager.io/docs/installation/helm/).\nAdd the CRDs under the `template` directory and have a spec in the `values.yaml`\nwhich will define if the CRDs should or not be applied\n\n```shell\nhelm install|upgrade \\\n  myrelease \\\n  --namespace my-namespace \\\n  --set `crds.enabled=true`\n```\n\nAlso, add another spec to the `values.yaml` to not allow the CRDs\nbe deleted when the helm is uninstalled:\n\n```yaml\n  # START annotations {{- if .Values.crds.keep }}\n  annotations:\n    helm.sh/resource-policy: keep\n  # END annotations {{- end }}\n```\n\nAdditionally, we might want to\nscaffold separate charts for the APIs and support both.\nAn example of this approach provided as feedback\nwas [karpenter-provider-aws](https://github.com/aws/karpenter-provider-aws/tree/main/charts).\n\nWe should either make clear the usage of both supported\nways and clarify their limitations. However, the proposed\nsolution would result in the following layout:\n\n```\nexample-project/\n  dist/\n    chart/\n    example-project-crd/\n      ├── Chart.yaml\n      ├── templates/\n      │   ├── _helpers.tpl\n      │   ├── crds/\n      │   │   └── <CRDs YAML files generated under config/crds/>\n      └── values.yaml\n    example-project/\n      ├── Chart.yaml\n      ├── templates/\n      │   ├── _helpers.tpl\n      │   ├── crds/\n      │   └── <CRDs YAML files generated under config/crds/>\n      │   ├── ...\n```\n\n### 2) How to manage dependencies such as Cert-Manager and Prometheus?\n\nHelm charts allow maintainers to define dependencies via the `Chart.yaml` file.\nHowever, in the initial version of this plugin at least, we do not need to consider management of dependencies.\n\nAdding dependencies such as **Cert-Manager** and **Prometheus** directly in the `Chart.yaml`\ncould introduce issues since these components are intended to be installed only once per cluster.\nAttempting to manage multiple installations could lead to conflicts and cause unintended behaviors,\nespecially in shared cluster environments.\n\nTo avoid these issues, the plugin for now will not scaffold this file and will not try to\nmanage it. Instead, users will be responsible for managing these dependencies outside of\nthe generated Helm chart, ensuring they are correctly installed and only installed once in the cluster.\n\n## Motivation\n\nCurrently, projects scaffolded with Kubebuilder can be distributed via YAML. Users can run\n`make build-installer IMG=<some-registry>/<project-name>:tag`, which will generate `dist/install.yaml`.\nTherefore, its consumers can install the solution by applying this YAML file, such as:\n`kubectl apply -f https://raw.githubusercontent.com/<org>/<project-name>/<tag or branch>/dist/install.yaml`.\n\nHowever, many adopt solutions require the Helm Chart format, such as FluxCD. Therefore,\nmaintainers are looking to also provide their solutions via Helm Chart. Users currently face the challenges of lacking\nan officially supported distribution mechanism for Helm Charts. They seek to:\n\n- Harness the power of Helm Chart as a package manager for the project, enabling seamless adaptation to diverse deployment environments.\n- Take advantage of Helm's dependency management capabilities to simplify the installation process of project dependencies, such as cert-manager.\n- Seamlessly integrate with Helm's ecosystem, including FluxCD, to efficiently manage the project.\n\nConsequently, this proposal aims to introduce a method that allows Kubebuilder users to easily distribute their projects through Helm Charts, a strategy that many well-known projects have adopted:\n\n- [mongodb](https://artifacthub.io/packages/helm/mongodb-helm-charts/community-operator)\n- [cert-manager](https://cert-manager.io/v1.6-docs/installation/helm/#1-add-the-jetstack-helm-repository)\n- [prometheus](https://bitnami.com/stack/prometheus-operator/helm)\n- [aws-load-balancer-controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller/tree/main/helm/aws-load-balancer-controller)\n\n**NOTE:** For further context see the [discussion topic](https://github.com/kubernetes-sigs/kubebuilder/discussions/3074)\n\n## Goals\n\n- Allow Kubebuilder users distribute their projects using Helm easily.\n- Make the best effort to preserve any customizations made to the Helm Charts by the users, which means we will skip syncs in the `values.ymal`.\n- Stick with Helm layout definitions and externalize into the relevant values-only options to distribute the default scaffold done by Kubebuilder. We should follow https://helm.sh/docs/chart_best_practices.\n\n## Non-Goals\n\n- Converting any Kustomize configuration to Helm Charts like [helmify](https://github.com/arttor/helmify) does.\n- Support the deprecated plugins. This option should be supported from `go/v4` and `kustomize/v2`\n- Introduce support for Helm in addition to Kustomize, or replace Kustomize with Helm entirely, similar to the approach\ntaken by Operator-SDK, thereby allowing users to utilize Helm Charts to build their Project.\n- Attend standard practices that deviate from Helm Chart layout, definition, or conventions to workaround its limitations.\n\n## User Stories\n\n- As a developer, I want to be able to generate a helm chart from a kustomize directory so that I can distribute the helm chart to my users. Also, I want the\n  generation to be as simple as possible without the need to write any additional duplicate files.\n- As a user, I want the helm chart can cover all potential configurations when I deploy it on the Kubernetes cluster.\n- As a platform engineer, I want to be able to manage different versions and configurations of a project across multiple clusters and environments based on the same distribution artifact (Helm Chart), with versioning and dependency locking for supply chain security.\n\n## Implementation Details/Notes/Constraints\n\n### Plugin Layout\n\n- **Location and Versioning**: The new plugin should follow Kubebuilder standards and\nbe implemented under `pkg/plugins/optional`. It should be introduced as an alpha version\n(`v1alpha`), similar to the [Grafana plugin](https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/plugins/optional/grafana/v1alpha).\n\n- **The data should be tracked in PROJECT File**: Usage of the plugin should be tracked in the `PROJECT`\nfile with the input via flags and options if required. Example entry in the `PROJECT` file:\n\n```yaml\n...\nplugins:\n  helm.go.kubebuilder.io/v1-alpha:\n    options: ## (If ANY)\n      <flag/key>: <value>\n```\n\nEnsure that user-provided input is properly tracked, similar to how it's done in\nother plugins [(see the code in the plugin.go)](https://github.com/kubernetes-sigs/kubebuilder/blob/c058fb95fe0ccd8d2a3147990251ca501df5eb26/pkg/plugins/golang/deploy-image/v1alpha1/plugin.go#L58-L75)\nand the [(code source to track the data)](https://github.com/kubernetes-sigs/kubebuilder/blob/c058fb95fe0ccd8d2a3147990251ca501df5eb26/pkg/plugins/golang/deploy-image/v1alpha1/api.go#L191-L217)\nof the deploy-image plugin for reference.\n\n**NOTE** We might not need options/flags in the first implementation. However, we should still track the plugin as\nwe do for the Grafana plugin.\n\n### Plugin Implementation Structure\n\nFollowing the structure implementation for the source code of this plugin:\n\n```shell\n.\n├── helm-chart\n│   └── v1alpha1\n│       ├── init.go\n│       ├── edit.go\n│       ├── plugin.go\n│       └── scaffolds\n│           ├── init.go\n│           ├── edit.go\n│           └── internal\n│               └── templates\n```\n\n### SubCommand implementation\n\nFor each subCommand we will need to check the resources which\nare scaffold for each subCommand via the [kustomize](https://kubebuilder.io/plugins/kustomize-v2) plugin\nand ensure that we will implement the subCommand of the HelmChart plugin\nto the respective scaffolds as well.\n\n#### To Sync the Manifests Created with `controller-gen`\n\nUsers will need to call the subcommand `edit` passing the plugin to\nensure that the Helm chart is properly synced.\n\nTherefore, the `PostScaffold` of this command could perform steps such as:\n\n- **Run `make manifests`**: Generate the latest CRDs and other manifests.\n- **Copy the files to Helm chart templates**:\n  - Copy CRDs: `cp config/crd/bases/*.yaml chart/example-project-crd/templates/crds/`\n  - Copy RBAC manifests: `cp config/rbac/*.yaml chart/example-project/templates/rbac/`\n  - Copy webhook configurations: `cp config/webhook/*.yaml chart/example-project/templates/webhook/`\n  - Copy the manager manifest: `cp config/default/manager.yaml chart/example-project/templates/manager/manager.ymal0`\n- **Replace placeholders with Helm values**: Ensure that customized fields, such as the namespace, are properly replaced accordingly.\n  Example: Replace `name: system` with `{{ .Values.Release.name }}`.\n\nThis ensures the Helm chart is always up-to-date with the latest\nmanifests generated by Kubebuilder, maintaining consistency with the\nconfigured namespace and other customizable fields.\n\nWe will need to use the utils helpers such as [ReplaceInFile](https://github.com/kubernetes-sigs/kubebuilder/blob/c058fb95fe0ccd8d2a3147990251ca501df5eb26/pkg/plugin/util/util.go#L303-L323)\nor [EnsureExistAndReplace](https://github.com/kubernetes-sigs/kubebuilder/blob/c058fb95fe0ccd8d2a3147990251ca501df5eb26/pkg/plugin/util/util.go#L276)\nto achieve this goal.\n\n### HelmChart Values Scaffolded by the Plugin\n\n- **Allow values.yaml to be fully re-generated with the flag --force**:\n\nBy default, the `values.yaml` file should not\nbe overwritten. However, users should have the option to overwrite it using\na flag (`--force=true`).\n\nThis can be implemented in the specific template as done for other plugins:\n\n```go\nif f.Force {\n    f.IfExistsAction = machinery.OverwriteFile\n} else {\n    f.IfExistsAction = machinery.Error\n}\n```\n\n**NOTE:** We will evaluate the cases when we implement `webhook.go` and `api.go`\nfor the HelmChart plugin. However, we might use the force flag to replicate\nthe same behavior implemented in the subCommands of the kustomize plugin.\nFor instance, if the flag is used when creating an API, it forces\nthe overwriten of the generated samples. Similarly, if the api subCommand\nof the HelmChart plugin is called with `--force`, we should replace\nall samples with the latest versions instead of only adding the new one.\n\n- **Helm Chart Templates should have conditions**:\n\nEnsure templates install resources based on\nconditions defined in the `values.yaml`. Example for CRDs:\n\n```\n# To install CRDs\n{{- if .Values.crd.enable }}\n...\n{{- end }}\n```\n\n- **Customizable Values**: Set customizable values in the `values.yaml`,\n  such as defining ServiceAccount names, and whether they should be created or not.\n  Furthermore, we should include comments to help end-users understand the source\n  of configurations. Example:\n\n```yaml\n{{- if .Values.rbac.enable }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ .Values.rbac.serviceAccountName }}\n  namespace: {{ .Release.Namespace }}\n{{- end }}\n```\n\n- **Example of values.yaml**:\n\nFollowing an example to illustrate the expected result of this plugin:\n\n```yaml\n# Install CRDs under the template\ncrd:\n  enable: false\n  keep: true\n\n# Webhook configuration sourced from the `config/webhook`\nwebhook:\n  enabled: true\n  conversion:\n    enabled: true\n\n## RBAC configuration under the `config/rbac` directory\nrbac:\n  create: true\n  serviceAccountName: \"controller-manager\"\n\n# Cert-manager configuration\ncertmanager:\n  enabled: false\n  issuerName: \"letsencrypt-prod\"\n  commonName: \"example.com\"\n  dnsName: \"example.com\"\n\n# Network policy configuration sourced from the `config/network_policy`\nnetworkPolicy:\n  enabled: false\n\n# Prometheus configuration\nprometheus:\n  enabled: false\n\n# Manager configuration sourced from the `config/manager`\nmanager:\n  replicas: 1\n  image:\n    repository: \"controller\"\n    tag: \"latest\"\n  resources:\n    limits:\n      cpu: 100m\n      memory: 128Mi\n    requests:\n      cpu: 100m\n      memory: 64Mi\n\n# Metrics configuration sourced from the `config/metrics`\nmetrics:\n  enabled: true\n\n# Leader election configuration sourced from the `config/leader_election`\nleaderElection:\n  enabled: true\n  role: \"leader-election-role\"\n  rolebinding: \"leader-election-rolebinding\"\n\n\n# Controller Manager configuration sourced from the `config/manager`\ncontrollerManager:\n  manager:\n    args:\n    - --metrics-bind-address=:8443\n    - --leader-elect\n    - --health-probe-bind-address=:8081\n    containerSecurityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        drop:\n        - ALL\n    image:\n      repository: controller\n      tag: latest\n    resources:\n      limits:\n        cpu: 500m\n        memory: 128Mi\n      requests:\n        cpu: 10m\n        memory: 64Mi\n  replicas: 1\n  serviceAccount:\n    annotations: {}\n\n# Kubernetes cluster domain configuration\nkubernetesClusterDomain: cluster.local\n\n# Metrics service configuration sourced from the `config/metrics`\nmetricsService:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  type: ClusterIP\n\n# Webhook service configuration sourced from the `config/webhook`\nwebhookService:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  type: ClusterIP\n```\n\n### Optional configurations should be disabled by default\n\nThe HelmChart plugin should not scaffold optional options enabled\nwhen those are scaffolded as disabled by the default implementation\nof `kustomize/v2` and consequently the `go/v4` plugin used by default. Example:\n\nThe dependency on Cert-Manager is disabled by default.\n\n```yaml\nFrom config/default/kusyomization.yaml\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n#- ../certmanager\n```\n\nTherefore, by default the `values.yaml` should be scaffolded with:\n\n```\n# Cert-manager configuration\ncertmanager:\n  enabled: false\n```\n\n### Layout of the Helm-Chart\n\nFollowing an example of the expected result of this plugin:\n\n```shell\nexample-project/\n  dist/\n  chart/\n    example-project-crd/\n      ├── Chart.yaml\n      ├── templates/\n      │   ├── _helpers.tpl\n      │   ├── crds/\n      │   │   └── <CRDs YAML files generated under config/crds/>\n      └── values.yaml\n    example-project/\n      ├── Chart.yaml\n      ├── templates/\n      │   ├── _helpers.tpl\n      │   ├── crds/\n      │   └── <CRDs YAML files generated under config/crds/>\n      │   ├── certmanager/\n      │   │   └── certificate.yaml\n      │   ├── manager/\n      │   │   └── manager.yaml\n      │   ├── network-policy/\n      │   │   ├── allow-metrics-traffic.yaml\n      │   │   └── allow-webhook-traffic.yaml // Should be added by the plugin subCommand webhook.go\n      │   ├── prometheus/\n      │   │   └── monitor.yaml\n      │   ├── rbac/\n      │   │   ├── kind_editor_role.yaml\n      │   │   ├── kind_viewer_role.yaml\n      │   │   ├── leader_election_role.yaml\n      │   │   ├── leader_election_role_binding.yaml\n      │   │   ├── metrics_auth_role.yaml\n      │   │   ├── metrics_auth_role_binding.yaml\n      │   │   ├── metrics_reader_role.yaml\n      │   │   ├── role.yaml\n      │   │   ├── role_binding.yaml\n      │   │   └── service_account.yaml\n      │   ├── samples/\n      │   │   └── kind_version_admiral.yaml\n      │   ├── webhook/\n      │   │   ├── manifests.yaml\n      │   │   └── service.yaml\n      └── values.yaml\n```\n\n### Update the README\n\nA README.md is scaffold for the projects. (see its implementation [here](https://github.com/kubernetes-sigs/kubebuilder/blob/master/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go)).\nTherefore, if the project is scaffold with the HelmChart plugin then,\nwe should update the [Distribution](https://github.com/kubernetes-sigs/kubebuilder/blob/master/testdata/project-v4/README.md#project-distribution)\nsection to add the info and steps over how to keep the HelmChart synced.\n\n### Tests and samples\n\nTo ensure that the new plugin will work well we will need to:\n- Implement e2e tests for the plugin. (for reference see the e2e tests for the [DeployImage](https://github.com/kubernetes-sigs/kubebuilder/tree/master/test/e2e/deployimage))\n- Ensure that the plugin is scaffold with all samples under the [testdata](https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata) directory (we will need call the plugin in [test/testdata/generate.sh](https://github.com/kubernetes-sigs/kubebuilder/blob/master/test/testdata/generate.sh#L115-L119))\n\n### Documentation\n\nThe new plugin should either be properly documented such as the others.\nFor reference see:\n- [Available Plugins](https://book.kubebuilder.io/plugins/available-plugins)\n- [DeployImage Plugin](https://book.kubebuilder.io/plugins/deploy-image-plugin-v1-alpha)\n\n## Risks and Mitigations\n\n**Difficulty in Maintaining the Solution**\n\nMaintaining the solution may prove challenging in the long term,\nparticularly if it does not gain community adoption and, consequently, collaboration.\nTo mitigate this risk, the proposal aims to introduce an optional alpha plugin or to\nimplement it through an alpha command. This approach provides us with greater flexibility\nto make adjustments or, if necessary, to deprecate the feature without definitively\ncompromising support.\n\n## Proof of Concept\n\nIn order to prove that would be possible we could\nrefer to the open source tool\n[helmify](https://github.com/arttor/helmify).\n\n## Drawbacks\n\n**Inability to Handle Complex Kubebuilder Scenarios**\n\nThe proposed plugin may struggle to appropriately handle complex scenarios commonly encountered\nin Kubebuilder projects, such as intricate webhook configurations. Kubebuilder’s scaffolded\nprojects can have sophisticated webhook setups, and translating these accurately into Helm\nCharts may prove challenging. This could result in Helm Charts that are not fully reflective\nof the original project’s functionality or configurations.\n\n**Incomplete Generation of Valid and Deployable Helm Charts**\n\nThe proposed solution may not be capable of generating a fully valid and deployable Helm Chart\nfor all use cases supported by Kubebuilder. Given the diversity and complexity of potential\nconfigurations within Kubebuilder projects, there is a risk that the generated Helm Charts\nmay require significant manual intervention to be functional. This drawback undermines the\ngoal of simplifying distribution via Helm Charts and could lead to frustration for users who\nexpect a seamless and automated process.\n\n## Alternatives\n\n**Via a new command (Alternative Option)**\n\nBy running the following command, the plugin will generate a helm chart from the specific kustomize directory and output it to the directory specified by the `--output` flag.\n\n```shell\nkubebuilder alpha generate-helm-chart --from=<path> --output=<path>\n```\n\nThe main drawback of this option is that it does not adhere to the Kubebuilder ecosystem.\nAdditionally, we would not take advantage of Kubebuilder library features, such as avoiding\noverwriting the `values.yaml`. It might also be harder to support and maintain since we would\nnot have the templates as we usually do.\n\nLastly, another con is that it would not allow us to scaffold projects with the plugin\nenabled and in the future provide further configurations and customizations for this plugin.\nThese configurations would be tracked in the `PROJECT` file, allowing integration with other\nprojects, extensions, and the re-scaffolding of the HelmChart while preserving the inputs\nprovided by the user via plugins flags as it is done for example for\nthe [Deploy Image](https://book.kubebuilder.io/plugins/deploy-image-plugin-v1-alpha) plugin.\n"
  },
  {
    "path": "designs/helper_to_upgrade_projects_by_rescaffolding.md",
    "content": "| Authors                            | Creation Date | Status      | Extra |\n|------------------------------------|---------------|-------------|---|\n| @camilamacedo86,@Kavinjsir,@varshaprasad96 | Feb, 2023     | Implementable | - |\n\nExperimental Helper to upgrade projects by re-scaffolding\n===================\n\nThis proposal aims to provide a new alpha command with a helper which\nwould be able to re-scaffold the project from the scratch based on\nthe [PROJECT config][project-config].\n\n## Example\n\nBy running a command like following, users would be able to re-scaffold the whole project from the scratch using the\ncurrent version of KubeBuilder binary available.\n\n```shell\nkubebuilder alpha generate [OPTIONS]\n```\n\n### Workflows\n\nFollowing some examples of the workflows\n\n**To update the project with minor changes provided**\n\nSee that for each KubeBuilder release the plugins versions used to scaffold\nthe projects might have bug fixes and new incremental features added to the\ntemplates which will result in changes to the files that are generated by\nthe tool for new projects.\n\nIn this case, you previously used the tool to generate the project\nand now would like to update your project with the latest changes\nprovided for the same plugin version. Therefore, you will need to:\n\n- Download and install KubeBuilder binary ( latest / upper release )\n- You will run the command in the root directory of your project: `kubebuilder alpha generate`\n- Then, the command will remove the content of your local directory and re-scaffold the project from the scratch\n- It will allow you to compare your local branch with the remote branch of your project to re-add the code on top OR\n  if you do not use the flag `--no-backup`, then you can compare the local directory with the copy of your project\n  copied to the path `.backup/project-name/` before the re-scaffold is done.\n- Therefore, you can run make all and test the final result. You will have after all your project updated.\n\n**To update the project with major changes provided**\n\nIn this case, you are looking for to migrate the project from, for example,\n`go/v3` to `go/v4`. The steps are very similar to the above ones. However,\nin this case you need to inform the plugin that you want to use to do the scaffold\nfrom scratch `kubebuilder alpha generate --plugins=go/v4`.\n\n## Open Questions\n\nN/A\n\n## Summary\n\nTherefore, a new command can be designed to load user configs from the [PROJECT config][project-config] file, and run the corresponding kubebuilder subcommands to generate the project based on the new kubebuilder version. Thus, it makes it easier for the users to migrate their operator projects to the new scaffolding.\n\n## Motivation\n\nA common scenario is to upgrade the project based on the newer Kubebuilder. The recommended (straightforward) steps are:\n\n- a) re-scaffold all files from scratch using the upper version/plugins\n- b) copy user-defined source code to the new layout\n\nThe proposed command will automate the process at maximum, therefore helping operator authors with minimizing the manual effort.\n\nThe main motivation of this proposal is to provide a helper for upgrades and\nmake this process less painful. Examples:\n\n- See the discussion [How to regenerate scaffolding?](https://github.com/kubernetes-sigs/kubebuilder/discussions/2864)\n- From [slack channel By Paul Laffitte](https://kubernetes.slack.com/archives/CAR30FCJZ/p1675166014762669)\n\n### Goals\n\n- Help users upgrade their project with the latest changes\n- Help users re-scaffold projects from scratch based on what was done previously with the tool\n- Make the upgrade process less painful\n\n### Non-Goals\n\n- Change the default layout or how the KubeBuilder CLI works\n- Deal with customizations or deviations from the proposed layout\n- Be able to perform the project upgrade to the latest changes without human interactions\n- Deal and support external plugins\n- Provide support to older version before having the Project config (Kubebuilder < 3x) and the go/v2 layout which exists to ensure  a backwards compatibility with legacy layout provided by Kubebuilder 2x\n\n## Proposal\n\nThe proposed solution to achieve this goal is to create an alpha command as described\nin the example section above, see:\n\n```shell\nkubebuilder alpha generate \\\n    --input-dir=<path where the PROJECT file can be found>\n    --output-dir=<path where the project should be re-scaffold>\n    --no-backup\n    --backup-path=<path-where the current version of the project should be copied as backup>\n    --plugins=<chain of plugins key that can be used to create the layout with init sub-command>\n```\n\n**Where**:\n\n- input-dir: [Optional] If not informed, then, by default, it is the current directory (project directory). If the `PROJECT` file does not exist, it will fail.\n- output-dir: [Optional] If not informed then, it should be the current repository.\n- no-backup: [Optional] If not informed then, the current directory should be copied to the path `.backup/project-name`\n- backup: [Optional] If not informed then, the backup will be copied to the path `.backup/project-name`\n- plugins:  [Optional] If not informed then, it is the same plugin chain available in the layout field\n- binary: [Optional] If not informed then, the command will use KubeBuilder binary installed globally.\n\n> Note that the backup created in the current directory must be prefixed with `.`. Otherwise the tool\nwill not able to perform the scaffold to create a new project from the scratch.\n\nThis command would mainly perform the following operations:\n\n- 1. Check the flags\n- 2. If the backup flag be used, then check if is a valid path and make a backup of the current project\n- 3. Copy the whole current directory to `.backup/project-name`\n- 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.\nOnly the content under `.backup/project-name` should be kept.\n- 5. Read the [PROJECT config][project-config]\n- 6. Re-run all commands using the KubeBuilder binary to recreate the project in the output directory\n\nThe command should also provide comprehensive help with examples of the proposed workflows. So that, users\nare able to understand how to use it when run `--help`.\n\n### User Stories\n\n**As an Operator author:**\n\n- I can re-generate my project from scratch based on the proposed helper, which executes all the\ncommands according to my previous input to the project. That way, I can easily migrate my project to the new layout\nusing the newer CLI/plugin versions, which support the latest changes, bug fixes, and features.\n- I can regenerate my project from the scratch based on all commands that I used the tool to build\nmy project previously but informing a new init plugin chain, so that I could upgrade my current project to new\nlayout versions and experiment alpha ones.\n- I would like to re-generate the project from the scratch using the same config provide in the PROJECT file and inform\na path to do a backup of my current directory so that I can also use the backup to compare with the new scaffold and add my custom code\non top again without the need to compare my local directory and new scaffold with any outside source.\n\n**As a Kubebuiler maintainer:**\n\n- I can leverage this helper to easily migrate tutorial projects of the Kubebuilder book.\n- I can leverage on this helper to encourage its users to migrate to upper versions more often, making it easier to maintain the project.\n\n### Implementation Details/Notes/Constraints\n\nNote that in the [e2e tests](https://github.com/kubernetes-sigs/kubebuilder/tree/master/test/e2e) the binary is used to do the scaffolds.\nAlso, very similar to the implementation that exist in the integration test KubeBuilder has\na code implementation to re-generate the samples used in the docs and add customizations on top,\nfor further information check the [hack/docs](https://github.com/kubernetes-sigs/kubebuilder/tree/master/hack/docs).\n\nThis subcommand could have a similar implementation that could be used by the tests and this plugin.\nNote that to run the commands using the binaries we are mainly using the following golang implementation:\n\n```go\ncmd := exec.Command(t.BinaryName, Options)\n_, err := t.Run(cmd)\n```\n\n### Risks and Mitigations\n\n**Hard to keep the command maintained**\n\nA risk to consider is that it would be hard to keep this command maintained\nbecause we need to develop specific code operations for each plugin. The mitigation for\nthis problem could be developing a design more generic that could work with all plugins.\n\nHowever, initially a more generic design implementation does not appear to be achievable and\nwould be considered out of the scope of this proposal (no goal). It should to be considered\nas a second phase of this implementation.\n\nTherefore, the current achievable mitigation in place is that KubeBuilder's  policy of not providing official\nsupport of maintaining and distributing many plugins.\n\n### Proof of Concept\n\nAll input data is tracked. Also, as described above we have examples of code implementation\nthat uses the binary to scaffold the projects. Therefore, the goal of this project seems\nvery reasonable and achievable. An initial work to try to address this requirement can\nbe checked in this [pull request](https://github.com/kubernetes-sigs/kubebuilder/pull/3022)\n\n## Drawbacks\n\n- If the value that feature provides does not pay off the effort to keep it\n  maintained, then we would need to deprecate and remove the feature in the long term.\n\n## Alternatives\n\nN/A\n\n## Implementation History\n\nThe idea of automate the re-scaffold of the project is what motivates\nus track all input data in to the [project config][project-config]\nin the past. We also tracked the [issue](https://github.com/kubernetes-sigs/kubebuilder/issues/2068)\nbased on discussion that we have to indeed try to add further\nspecific implementations to do operations per major bumps. For example:\n\nTo upgrade from go/v3 to go/v4 we know exactly what are the changes in the layout\nthen, we could automate these specific operations as well. However, this first idea is harder yet\nto be addressed and maintained.\n\n## Future Vision\n\nWe could use it to do cool future features such as creating a GitHub action which would push-pull requests against the project repositories to help users be updated with, for example, minor changes. By using this command, we might able to git clone the project and to do a new scaffold and then use some [git strategy merge](https://www.geeksforgeeks.org/merge-strategies-in-git/) to result in a PR to purpose the required changes.\n\nWe probably need to store the CLI tool tag release used to do the scaffold to persuade this idea. So that we can know if the project requires updates or not.\n\n[project-config]: https://book.kubebuilder.io/reference/project-config.html\n"
  },
  {
    "path": "designs/integrating-kubebuilder-and-osdk.md",
    "content": "| Authors       | Creation Date | Status      | Extra |\n|---------------|---------------|-------------|-------|\n| @joelanford |  Sep 6, 2019  | implemented | -     |\n\n# Integrating Kubebuilder and Operator SDK\n\n## Goal\n\nTo 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.\n\n## Background\n\nKubebuilder and [Operator SDK][operator-sdk] are similar projects meant to simplify the process of building a Kubernetes operator (or controller). Both projects make extensive use of the upstream controller-runtime and controller-tools projects, and therefore scaffold similar Go source files and package structures.\n\n## Motivation\n\nThe Operator SDK and Kubebuilder contributors collaborate on improvements to their shared upstream dependencies, but there is significant overlap between Operator SDK and Kubebuilder related to scaffolding Go operators. Both projects have commands to initialize a new project and add boilerplate implementations of new APIs and controllers.\n\nThe motivation for integrating Kubebuilder and Operator SDK is that rather than duplicating work related to project scaffolding of Go operators, the projects could work together on one implementation, which would speed up progress and likely result in a more general solution.\n\n## Integration Plan\n\nThe Kubebuilder and Operator SDK contributors created a [GitHub project][kb-osdk-github-project] to track the work necessary to align the projects. There are three main themes.\n\n### Upstream code from Operator SDK\n\nThe 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:\n* A `DynamicRESTMapper` that enables an operator to dynamically and automatically discover new CRDs added to the cluster after the operator has started\n* A `GenerationChangedPredicate` that can trigger reconciliation events when a resource's `metadata.generation` field has changed\n* Flags and helpers that can be used to provide more fine-grained configuration when constructing the default `zap`-based logger\n\nThe 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.\n\n### Prototypes\n\nTo 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.\n\nMore 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.\n\n### Documentation\n\nOperator SDK and Kubebuilder currently maintain separate documentation even though a significant chunk of it overlaps. By combining efforts, the SDK contributors will migrate and integrate their Go-based operator documentation upstream into the Kubebuilder documentation and join the Kubebuilder contributors in keeping it up-to-date.\n\n[operator-sdk]: https://github.com/operator-framework/operator-sdk\n[kb-osdk-github-project]: https://github.com/kubernetes-sigs/kubebuilder/projects/7\n[addon-pattern-pr]: https://github.com/kubernetes-sigs/kubebuilder/pull/943\n"
  },
  {
    "path": "designs/simplified-scaffolding.md",
    "content": "| Authors       | Creation Date | Status      | Extra |\n|---------------|---------------|-------------|---|\n| @DirectXMan12 | Mar 6, 2019 | Implemented | - |\n\nSimplified Builder-Based Scaffolding\n====================================\n\n## Background\n\nThe current scaffolding in kubebuilder produces a directory structure that\nlooks something like this (compiled artifacts like config omitted for\nbrevity):\n\n<details>\n\n<summary>`tree -d ./test/project`</summary>\n\n```shell\n$ tree -d ./test/project\n./test/project\n├── cmd\n│   └── manager\n├── pkg\n│   ├── apis\n│   │   ├── creatures\n│   │   │   └── v2alpha1\n│   │   ├── crew\n│   │   │   └── v1\n│   │   ├── policy\n│   │   │   └── v1beta1\n│   │   └── ship\n│   │       └── v1beta1\n│   ├── controller\n│   │   ├── firstmate\n│   │   ├── frigate\n│   │   ├── healthcheckpolicy\n│   │   ├── kraken\n│   │   └── namespace\n│   └── webhook\n│       └── default_server\n│           ├── firstmate\n│           │   └── mutating\n│           ├── frigate\n│           │   └── validating\n│           ├── kraken\n│           │   └── validating\n│           └── namespace\n│               └── mutating\n└── vendor\n```\n\n</details>\n\nAPI packages have a separate file for each API group that creates a SchemeBuilder,\na separate file to aggregate those scheme builders together, plus files for types,\nand the per-group-version scheme builders as well:\n\n<details>\n\n<summary>`tree ./test/project/pkg/apis`</summary>\n\n```shell\n$ ./test/project/pkg/apis\n├── addtoscheme_creatures_v2alpha1.go\n├── apis.go\n├── creatures\n│   ├── group.go\n│   └── v2alpha1\n│       ├── doc.go\n│       ├── kraken_types.go\n│       ├── kraken_types_test.go\n│       ├── register.go\n│       ├── v2alpha1_suite_test.go\n│       └── zz_generated.deepcopy.go\n...\n```\n\n</details>\n\nController packages have a separate file that registers each controller with a global list\nof controllers, a file that provides functionality to register that list with a manager,\nas well as a file that constructs the individual controller itself:\n\n<details>\n\n<summary>`tree ./test/project/pkg/controller`</summary>\n\n```shell\n$ tree ./test/project/pkg/controller\n./test/project/pkg/controller\n├── add_firstmate.go\n├── controller.go\n├── firstmate\n│   ├── firstmate_controller.go\n│   ├── firstmate_controller_suite_test.go\n│   └── firstmate_controller_test.go\n...\n```\n\n</details>\n\n## Motivation\n\nThe current scaffolding in Kubebuilder has two main problems:\ncomprehensibility and dependency passing.\n\n### Complicated Initial Structure\n\nWhile the structure of Kubebuilder projects will likely feel at home for\nexisting Kubernetes contributors (since it matches the structure of\nKubernetes itself quite closely), it provides a fairly convoluted\nexperience out of the box.\n\nEven for a single controller and API type (without a webhook), it\ngenerates 8 API-related files and 5 controller-related files.  Of those\nfiles, 6 are Kubebuilder-specific glue code, 4 are test setup, and\n1 contains standard Kubernetes glue code, leaving only 2 with actual\nuser-edited code.\n\nThis proliferation of files makes it difficult for users to understand how\ntheir code relates to the library, posing some barrier for initial adoption\nand moving beyond a basic knowledge of functionality to actual\nunderstanding of the structure.  A common line of questioning amongst\nnewcomers to Kubebuilder includes \"where should I put my code that adds\nnew types to a scheme\" (and similar questions), which indicates that it's\nnot immediately obvious to these users why the project is structured the\nway it is.\n\nAdditionally, we scaffold out API \"tests\" that test that the API server is\nable to receive create requests for the objects, but don't encourage\nmodification beyond that.  An informal survey seems to indicate that most\nusers don't actually modify these tests (many repositories continue to\nlook like\n[this](https://github.com/replicatedhq/gatekeeper/blob/3bfe0f7213b6d41abf2df2a6746f3351e709e6ff/pkg/apis/policies/v1alpha2/admissionpolicy_types_test.go)).\nIf we want to help users test that their object's structure is the way\nthey think it is, we're probably better served coming up with a standard\n\"can I create this example YAML file\".\n\nFurthermore, since the structure is quite convoluted, it makes it more\ndifficult to write examples, as the actual code we care about ends up\nscattered deep in the folder structure.\n\n### Lack of Builder\n\nWe introduced the builder pattern for controller construction in\ncontroller-runtime\n([GoDoc](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/builder?tab=doc#ControllerManagedBy))\nas a way to simplify construction of controllers and reduce boilerplate\nfor the common cases of controller construction.  Informal feedback from\nthis has been positive, and it enables fairly rapid, clear, and concise\nconstruction of controllers (e.g. this [one file\ncontroller](https://github.com/DirectXMan12/sample-controller/blob/workshop/main.go)\nused as a getting started example for a workshop).\n\nCurrent Kubebuilder scaffolding does not take advantage of the builder,\nleaving generated code using the lower-level constructs which require more\nunderstanding of the internals of controller-runtime to comprehend.\n\n### Dependency Passing Woes\n\nAnother common line of questioning amongst Kubebuilder users is \"how to\nI pass dependencies to my controllers?\".  This ranges from \"how to I pass\ncustom clients for the software I'm running\" to \"how to I pass\nconfiguration from files and flags down to my controllers\" (e.g.\n[kubernete-sigs/kubebuilder#611](https://github.com/kubernetes-sigs/kubebuilder/issues/611)\n\nSince reconciler implementations are initialized in `Add` methods with\nstandard signatures, dependencies cannot be passed directly to\nreconcilers.  This has lead to requests for dependency injection in\ncontroller-runtime (e.g.\n[kubernetes-sigs/controller-runtime#102](https://github.com/kubernetes-sigs/controller-runtime/issues/102)),\nbut in most cases, a structure more amicable to passing in the\ndependencies directly would solve the issue (as noted in\n[kubernetes-sigs/controller-runtime#182](https://github.com/kubernetes-sigs/controller-runtime/pull/182#issuecomment-442615175)).\n\n## Revised Structure\n\nIn the revised structure, we use the builder pattern to focus on the\n\"code-refactor-code-refactor\" cycle: start out with a simple structure,\nrefactor out as your project becomes more complicated.\n\nUsers receive a simply scaffolded structure to start. Simple projects can\nremain relatively simple, and complicated projects can decide to adopt\na different structure as they grow.\n\nThe new scaffold project structure looks something like this (compiled\nartifacts like config omitted for brevity):\n\n```shell\n$ tree ./test/project\n./test/project\n├── main.go\n├── controller\n│   ├── mykind_controller.go\n│   ├── mykind_controller_test.go\n│   └── controllers_suite_test.go\n├── api\n│   └── v1\n│       └── mykind_types.go\n│       └── groupversion_info.go\n└── vendor\n```\n\nIn this new layout, `main.go` constructs the reconciler:\n\n```go\n// ...\nfunc main() {\n\t// ...\n\terr := (&controllers.MyReconciler{\n\t\tMySuperSpecialAppClient: doSomeThingsWithFlags(),\n\t}).SetupWithManager(mgr)\n\t// ...\n}\n```\n\nwhile `mykind_controller.go` actually sets up the controller using the\nreconciler:\n\n```go\nfunc (r *MyReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.MyAppType{}).\n\t\tOwns(&corev1.Pod{}).\n\t\tComplete(r)\n}\n```\n\nThis makes it abundantly clear where to start looking at the code\n(`main.go` is the defacto standard entry-point for many go programs), and\nsimplifies the levels of hierarchy.  Furthermore, since `main.go` actually\ninstantiates an instance of the reconciler, users are able to add custom\nlogic having to do with flags.\n\nNotice that we explicitly construct the reconciler in `main.go`, but put\nthe setup logic for the controller details in `mykind_controller.go`. This\nmakes testing easier (see\n[below](#put-the-controller-setup-code-in-main-go)), but still allows us\nto pass in dependencies from `main`.\n\n### Why don't we...\n\n#### Put the controller setup code in main.go\n\nWhile this is an attractive pattern from a prototyping perspective, it\nmakes it harder to write integration tests, since you can't easily say\n\"run this controller with all its setup in processes\".  With a separate\n`SetupWithManager` method associated with reconcile, it becomes fairly\neasy to setup with a manager.\n\n#### Put the types directly under api/, or not have groupversion_info.go\n\nThese suggestions make it much harder to scaffold out additional versions\nand kinds.  You need to have each version in a separate package, so that\ntype names don't conflict.  While we could put scheme registration in with\n`kind_types.go`, if a project has multiple \"significant\" Kinds in an API\ngroup, it's not immediately clear which file has the scheme registration.\n\n#### Use a single types.go file\n\nThis works fine when you have a single \"major\" Kind, but quickly grows\nunwieldy when you have multiple major kinds and end up with\na hundreds-of-lines-long `types.go` file (e.g. the `appsv1` API group in\ncore Kubernetes).  Splitting out by \"major\" Kind (`Deployment`,\n`ReplicaSet`, etc) makes the code organization clearer.\n\n#### Change the current scaffold to just make Add a method on the reconciler\n\nWhile this solves the dependency issues (mostly, since you might want to\nfurther pass configuration to the setup logic and not just the runtime\nlogic), it does not solve the underlying pedagogical issues around the\ninitial structure burying key logic amidst a sprawl of generated files and\ndirectories.\n\n### Making this work with multiple controllers, API versions, API groups, etc\n\n#### Versions\n\nMost projects will eventually grow multiple API versions.  The only\nwrinkle here is making sure API versions get added to a scheme.  This can\nbe solved by adding a specially-marked init function that registration\nfunctions get added to (see the example).\n\n#### Groups\n\nSome projects eventually grow multiple API groups.  Presumably, in the\ncase of multiple API groups, the desired hierarchy is:\n\n```shell\n$ tree ./test/project/api\n./test/project/api\n├── groupa\n│   └── v1\n│       └── types.go\n└── groupb\n    └── v1\n        └── types.go\n```\n\nThere are three options here:\n\n1. Scaffold with the more complex API structure (this looks pretty close\n   to what we do today).  It doesn't add a ton of complexity, but does\n   bury types deeper in a directory structure.\n\n2. Try to move things and rename references.  This takes a lot more effort\n   on the Kubebuilder maintainers' part if we try to rename references\n   across the codebase.  Not so much if we force the user to, but that's\n   a poorer experience.\n\n3. Tell users to move things, and scaffold out with the new structure.\n   This is fairly messy for the user.\n\nSince growing to multiple API groups seems to be fairly uncommon, it's\nmostly like safe to take a hybrid approach here -- allow manually\nspecifying the output path, and, when not specified, asking the user to\nfirst restructure before running the command.\n\n#### Controllers\n\nMultiple controllers don't need their own package, but we'd want to\nscaffold out the builder.  We have two options here:\n\n1. Looking for a particular code comment, and appending a new builder\n   after it.  This is a bit more complicated for us, but perhaps provides\n   a nicer UX.\n\n2. Simply adding a new controller, and reminding the user to add the\n   builder themselves.  This is easier for the maintainers, but perhaps\n   a slightly poorer UX for the users.  However, writing out a builder by\n   hand is significantly less complex than adding a controller by hand in\n   the current structure.\n\nOption 1 should be fairly simple, since the logic is already needed for\nregistering types to the scheme, and we can always fall back to emitting\ncode for the user to place in manually if we can't find the correct\ncomment.\n\n### Making this work with Existing Kubebuilder Installations\n\nKubebuilder projects currently have a `PROJECT` file that can be used to\nstore information about project settings.  We can make use of this to\nstore a \"scaffolding version\", where we increment versions when making\nincompatible changes to how the scaffolding works.\n\nA missing scaffolding version field implies the version `1`, which uses\nour current scaffolding semantics.  Version `2` uses the semantics\nproposed here.  New projects are scaffolded with `2`, and existing\nprojects check the scaffold version before attempting to add addition API\nversions, controllers, etc\n\n### Teaching more complicated project structures\n\nSome controllers may eventually want more complicated project structures.\nWe should have a section of the book recommending options for when you\nproject gets very complicated.\n\n### Additional Tooling Work\n\n* Currently the `api/` package will need a `doc.go` file to make\n  `deepcopy-gen` happy.  We should fix this.\n\n* Currently, `controller-gen crd` needs the `api` directory to be\n  `pkg/apis/<group>/<version>`.  We should fix this.\n\n## Example\n\nSee #000 for an example with multiple stages of code generation\n(representing the examples is this form is rather complicated, since it\ninvolves multiple files).\n\n```shell\n$ kubebuilder init --domain test.k8s.io\n$ kubebuilder create api --group mygroup --version v1beta1 --kind MyKind\n$ kubebuilder create api --group mygroup --version v2beta1 --kind MyKind\n$ tree .\n.\n├── main.go\n├── controller\n│   ├── mykind_controller.go\n│   ├── controller_test.go\n│   └── controllers_suite_test.go\n├── api\n│   ├── v1beta1\n│   │   ├── mykind_types.go\n│   │   └── groupversion_info.go\n│   └── v1\n│       ├── mykind_types.go\n│       └── groupversion_info.go\n└── vendor\n```\n\n<details>\n\n<summary>main.go</summary>\n\n```go\npackage main\n\nimport (\n    \"os\"\n\n    ctrl \"sigs.k8s.io/controller-runtime\"\n    \"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n    \"k8s.io/apimachinery/pkg/runtime\"\n\n    \"my.repo/api/v1beta1\"\n    \"my.repo/api/v1\"\n    \"my.repo/controllers\"\n)\n\nvar (\n    scheme = runtime.NewScheme()\n    setupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n    v1beta1.AddToScheme(scheme)\n    v1.AddToScheme(scheme)\n    // +kubebuilder:scaffold:scheme\n}\n\nfunc main() {\n\tctrl.SetLogger(zap.New(zap.UseDevMode(true)))\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{Scheme: scheme})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"unable to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\terr = (&controllers.MyKindReconciler{\n\t\tClient: mgr.GetClient(),\n        log: ctrl.Log.WithName(\"mykind-controller\"),\n\t}).SetupWithManager(mgr)\n\tif err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"mykind\")\n\t\tos.Exit(1)\n\t}\n\n    // +kubebuilder:scaffold:builder\n\n\tsetupLog.Info(\"starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"problem running manager\")\n\t\tos.Exit(1)\n\t}\n}\n```\n\n</details>\n\n<details>\n\n<summary>mykind_controller.go</summary>\n\n```go\npackage controllers\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"github.com/go-logr/logr\"\n\n\t\"my.repo/api/v1\"\n)\n\ntype MyKindReconciler struct {\n\tclient.Client\n\tlog logr.Logger\n}\n\nfunc (r *MyKindReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {\n\tctx := context.Background()\n\tlog := r.log.WithValues(\"mykind\", req.NamespacedName)\n\n\t// your logic here\n\n\treturn req.Result{}, nil\n}\n\nfunc (r *MyKindReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(v1.MyKind{}).\n\t\tComplete(r)\n}\n```\n\n</details>\n\n`*_types.go` looks nearly identical to the current standard.\n\n<details>\n\n<summary>groupversion_info.go</summary>\n\n```go\npackage v1\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nvar (\n\tGroupVersion = schema.GroupVersion{Group: \"mygroup.test.k8s.io\", Version: \"v1\"}\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme\n\tSchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n```\n\n</details>\n"
  },
  {
    "path": "designs/template.md",
    "content": "| Authors       | Creation Date | Status      | Extra |\n|---------------|---------------|-------------|---|\n| @name | date | Implementable | - |\n\n# Title of the Design/Proposal\n\n<!-- Describe your change here.  This is purposefully freeform: we want\nenough information to evaluate the design, but not so much that you're\nannoyed by the overall design process and decide to bake cookies instead.\n-->\n\n## Example\n\n<!-- Specify an example of how the user would use this.  It helps other\ncontributors get a feel for how this will look in real code, and provides\na good opportunity to evaluate the end-user feel of the code for yourself.\n\nIf you find yourself groaning at verbosity, copy-and-pasting a lot, or\nwriting a bunch of tiny helper functions, it's a good indication that you\nmight need to re-evaluate the user experience of your design.\n\nThis is also a good opportunity to stop and write a proof-of-concept, if\nyou haven't already, which should help catch practical nits with the\ndesign. -->\n\n## Open Questions [optional]\n\n<!-- This is where to call out areas of the design that require closure before deciding\nto implement the design.  For instance,\n> 1. This requires exposing previously private resources which contain sensitive\n     information.  Can we do this?\n-->\n\n## Summary\n\n<!-- The `Summary` section is incredibly important for producing high quality\nuser-focused documentation such as release notes or a development roadmap. It\nshould be possible to collect this information before implementation begins in\norder to avoid requiring implementers to split their attention between writing\nrelease notes and implementing the feature itself.\n\nA good summary is probably at least a paragraph in length.-->\n\n## Motivation\n\n<!-- This section is for explicitly listing the motivation, goals and non-goals of\nthis proposal. Describe why the change is important and the benefits to users.-->\n\n### Goals\n\n<!-- List the specific goals of the proposal. How will we know that this has succeeded?-->\n\n### Non-Goals\n\n<!-- What is out of scope for this proposal? Listing non-goals helps to focus discussion\nand make progress.-->\n\n## Proposal\n\n<!-- This is where we get down to the nitty gritty of what the proposal actually is. -->\n\n### User Stories\n\n<!-- Detail the things that people will be able to do if this is implemented.\nInclude as much detail as possible so that people can understand the \"how\" of\nthe system. The goal here is to make this feel real for users without getting\nbogged down.\n\nUser story examples\n- As a user, I want to link the credit card to my profile so that I can pay for a rent faster, easier and without cash.\n- As a service provider, I want to add photos of my vehicles in the application so that I can attract more users.\n- As a user, I want several available vehicles to be displayed so that I can choose the most suitable option for me.\n\n- As a <role> I can <capability>, so that <receive benefit> -->\n\n### Implementation Details/Notes/Constraints [optional]\n\n<!-- What are the caveats to the implementation? What are some important details that\ndidn't come across above. Go in to as much detail as necessary here. This might\nbe a good place to talk about core concepts and how they relate. -->\n\n### Risks and Mitigations\n\n<!-- What are the risks of this proposal and how do we mitigate. Think broadly. For\nexample, consider both security and how this will impact the larger Operator Framework\necosystem.\n\nHow will security be reviewed and by whom? How will UX be reviewed and by whom?\n\nConsider including folks that also work outside your immediate sub-project. -->\n\n### Proof of Concept [optional]\n\n<!-- A demo showcasing a prototype of your design can be extremely useful to the\ncommunity when reviewing your proposal. There are many services that enable\nyou to record and share demos. Most OLM features can be showcased from the\ncommand line, making [https://asciinema.org](https://asciinema.org) an\nexcellent option to [record](https://asciinema.org/docs/usage) and\n[embed](https://asciinema.org/docs/embedding) your demo.\n\nBe sure to include:\n- An embedded recording of the prototype in action.\n- A link to the repository hosting the changes that the prototype introduces. -->\n\n## Drawbacks\n\n<!-- The idea is to find the best form of an argument why this enhancement should _not_ be implemented. -->\n\n## Alternatives\n\n<!-- Similar to the `Drawbacks` section the `Alternatives` section is used to\nhighlight and record other possible approaches to delivering the value proposed\nby an enhancement. -->\n"
  },
  {
    "path": "designs/update_action.md",
    "content": "| Authors         | Creation Date | Status      | Extra |\n|-----------------|---------------|-------------|-------|\n| @camilamacedo86 | 2024-11-07    | Implementable | - |\n| @vitorfloriano  |               | Implementable | - |\n\n# Proposal: Automating Operator Maintenance: Driving Better Results with Less Overhead\n\n## Introduction\n\nCode-generation tools like **Kubebuilder** and **Operator-SDK** have revolutionized cloud-native application development by providing scalable, community-driven frameworks. These tools simplify complexity, accelerate development, and enable developers to create tailored solutions while avoiding common pitfalls, establishing a strong foundation for innovation.\n\nHowever, as these tools evolve to keep up with ecosystem changes and new features, projects risk becoming outdated. Manual updates are time-consuming, error-prone, and create challenges in maintaining security, adopting advancements, and staying aligned with modern standards.\n\nThis project proposes an **automated solution for Kubebuilder**, with potential applications for similar tools or those built on its foundation. By streamlining maintenance, projects remain modern, secure, and adaptable, fostering growth and innovation across the ecosystem. The automation lets developers focus on what matters most: **building great solutions**.\n\n\n## Problem Statement\n\nKubebuilder is widely used for developing Kubernetes operators, providing a standardized scaffold. However, as the ecosystem evolves, keeping projects up-to-date presents challenges due to:\n\n- **Manual re-scaffolding processes**: These are time-intensive and error-prone.\n- **Increased risk of outdated configurations**: Leads to security vulnerabilities and incompatibility with modern practices.\n\n## Proposed Solution\n\nThis proposal introduces a **workflow-based tool** (such as a GitHub Action) that automates updates for Kubebuilder projects. Whenever a new version of Kubebuilder is released, the tool initiates a workflow that:\n\n1. **Detects the new release**.\n2. **Generates an updated scaffold**.\n3. **Performs a three-way merge to retain customizations**.\n4. **Creates a pull request (PR) summarizing the updates** for review and merging.\n\n## Example Usage\n\n### GitHub Actions Workflow:\n\n1. A user creates a project with Kubebuilder `v4.4.3`.\n2. When Kubebuilder `v4.5.0` is released, a **pull request** is automatically created.\n3. The PR includes scaffold updates while preserving the user’s customizations, allowing easy review and merging.\n\n### Local Tool Usage:\n\n1. A user creates a project with Kubebuilder `v4.4.3`\n2. When Kubebuilder `v4.5.0` is released, they run `kubebuilder alpha update` which calls `kubebuilder alpha generate` behind the scenes\n3. The tool updates the scaffold and preserves customizations for review and application.\n4. In case of conflicts, the tool allows users to resolve them before push a pull request with the changes.\n\n### Handling Merge Conflicts\n\n**Local Tool Usage**:\n\nIf conflicts cannot be resolved automatically, developers can manually address\nthem before completing the update.\n\n**GitHub Actions Workflow**:\n\nIf conflicts arise during the merge, the action will create a pull request and\nthe conflicst will be highlighted in the PR. Developers can then review and resolve\nthem. The PR will contains the default markers:\n\n**Example**\n\n```go\n<<<<<<< HEAD\n\t_ = logf.FromContext(ctx)\n=======\nlog := log.FromContext(ctx)\n>>>>>>> original\n```\n\n## Open Questions\n\n### 1. Do we need to create branches to perform the three-way merge,or can we use local temporary directories?\n\n> While temporary directories are sufficient for simple three-way merges, branches are better suited for complex scenarios.\n> They provide history tracking, support collaboration, integrate with CI/CD workflows, and offer more advanced\n> conflict resolution through Git’s merge command. For these reasons, it seems more appropriate to use branches to ensure\n> flexibility and maintainability in the merging process.\n\n> Furthermore, branches allows a better resolution strategy,\n> since allows us to use `kubebuilder alpha generate` command to-rescaffold the projects\n> using the same name directory and provide a better history for the PRs\n> allowing users to see the changes and have better insights for conflicts\n> resolution.\n\n### 2. What Git configuration options can facilitate the three-way merge?\n\nSeveral Git configuration options can improve the three-way merge process:\n\n```bash\n# Show all three versions (base, current, and updated) during conflicts\ngit config --global merge.conflictStyle diff3\n\n# Enable \"reuse recorded resolution\" to remember and reuse previous conflict resolutions\ngit config --global rerere.enabled true\n\n# Increase the rename detection limit to better handle renamed or moved files\ngit config --global merge.renameLimit 999999\n```\n\nThese configurations enhance the merging process by improving conflict visibility,\nreusing resolutions, and providing better file handling, making three-way\nmerges more efficient and developer-friendly.\n\n### 3. If we change Git configurations, can we isolate these changes to avoid affecting the local developer environment when the tool runs locally?\n\nIt seems that changes can be made using the `-c` flag, which applies the\nconfiguration only for the duration of a specific Git command. This ensures\nthat the local developer environment remains unaffected.\n\nFor example:\n\n```\ngit -c merge.conflictStyle=diff3 -c rerere.enabled=true merge\n```\n\n### 4. How can we minimize and resolve conflicts effectively during merges?\n\n- **Enable Git Features:**\n    - Use `git config --global rerere.enabled true` to reuse previous conflict resolutions.\n    - Configure custom merge drivers for specific file types (e.g., `git config --global merge.&lt;driver&gt;.name \"Custom Merge Driver\"`).\n\n- **Encourage Standardization:**\n    - Adopt a standardized scaffold layout to minimize divergence and reduce conflicts.\n\n- **Apply Frequent Updates:**\n    - Regularly update projects to avoid significant drift between the scaffold and customizations.\n\nThese strategies help minimize conflicts and simplify their resolution during merges.\n\n### 5. How to create the PR with the changes for projects that are monorepos?\nThat means the result of Kubebuilder is not defined in the root dir and might be in other paths.\n\nWe can define an `--output` directory and a configuration for the GitHub Action where\nusers will define where in their repo the path for the Kubebuilder project is.\nHowever, this might be out of scope for the initial version.\n\n### 6. How could AI help us solve conflicts? Are there any available solutions?\n\nWhile AI tools like GitHub Copilot can assist in code generation and provide suggestions,\nhowever, it might be risky be 100% dependent on AI for conflict resolution, especially in complex scenarios.\nTherefore, we might want to use AI as a complementary tool rather than a primary solution.\n\nAI can help by:\n- Providing suggestions for resolving conflicts based on context.\n- Analyzing code patterns to suggest potential resolutions.\n- Offering explanations for conflicts and suggesting best practices.\n- Assisting in summarizing changes.\n\n## Summary\n\n### Workflow Example:\n\n1. A developer creates a project with Kubebuilder `v4.4`.\n2. The tooling uses the release of Kubebuilder `v4.5`.\n3. The tool:\n   - Regenerates the original base source code for `v4.4` using the `clientVersion` in the `PROJECT` file.\n   - Generates the base source code for `v4.5`\n4. A three-way merge integrates the changes into the developer’s project while retaining custom code.\n5. The changes now can be packaged into a pull request, summarizing updates and conflicts for the developer’s review.\n\n### Steps:\n\nThe proposed implementation involves the following steps:\n\n1. **Version Tracking**:\n   - Record the `clientVersion` (initial Kubebuilder version) in the `PROJECT` file.\n   - Use this version as a baseline for updates.\n   - Available in the `PROJECT` file, from [v4.6.0](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v4.6.0) release onwards.\n\n2. **Scaffold Generation**:\n    - Generate the **original scaffold** using the recorded version.\n    - Generate the **updated scaffold** using the latest Kubebuilder release.\n\n3. **Three-Way Merge**:\n   - Ensure git is configured to handle three-way merges.\n   - Merge the original scaffold, updated scaffold, and the user’s customized project.\n   - Preserve custom code during the merge.\n\n4. **(For Actions) - Pull Request Creation**:\n   - Open a pull request summarizing changes, including details on conflict resolution.\n   - Schedule updates weekly or provide an on-demand option.\n\n#### Example Workflow\n\nThe following example code illustrates the proposed idea but has not been evaluated.\nThis is an early, incomplete draft intended to demonstrate the approach and basic concept.\n\nWe may want to develop a dedicated command-line tool, such as `kubebuilder alpha update`,\nto handle tasks like downloading binaries, merging, and updating the scaffold. In this approach,\nthe GitHub Action would simply invoke this tool to manage the update process and open the\nPull Request, rather than performing each step directly within the Action itself.\n\n```yaml\nname: Workflow Auto-Update\n\npermissions:\n  contents: write\n  pull-requests: write\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * 1\"    # Every Monday 00:00 UTC\n\njobs:\n  alpha-update:\n    runs-on: ubuntu-latest\n\n    steps:\n      # 1) Checkout the repository with full history\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          fetch-depth: 0\n\n      # 2) Install the latest stable Go toolchain\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: 'stable'\n\n      # 3) Install Kubebuilder CLI\n      - name: Install Kubebuilder\n        run: |\n          curl -L -o kubebuilder \"https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)\"\n          chmod +x kubebuilder\n          sudo mv kubebuilder /usr/local/bin/\n\n      # 4) Extract Kubebuilder version (e.g., v4.6.0) for branch/title/body\n      - name: Get Kubebuilder version\n        id: kb\n        shell: bash\n        run: |\n          RAW=\"$(kubebuilder version 2>/dev/null || true)\"\n          VERSION=\"$(printf \"%s\" \"$RAW\" | grep -oE 'v[0-9]+\\.[0-9]+\\.[0-9]+' | head -1)\"\n          echo \"version=${VERSION:-vunknown}\" >> \"$GITHUB_OUTPUT\"\n\n      # 5) Run kubebuilder alpha update\n      - name: Run kubebuilder alpha update\n        run: |\n          kubebuilder alpha update --force\n\n      # 6) Restore workflow files so the update doesn't overwrite CI config\n      - name: Restore workflows directory\n        run: |\n          git restore --source=main --staged --worktree .github/workflows\n          git add .github/workflows\n          git commit --amend --no-edit || true\n\n      # 7) Push to a versioned branch; create PR if missing, otherwise it just updates\n      - name: Push branch and create/update PR\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        shell: bash\n        run: |\n          set -euo pipefail\n          VERSION=\"${{ steps.kb.outputs.version }}\"\n          PR_BRANCH=\"kubebuilder-update-to-${VERSION}\"\n\n          # Create or update the branch and push\n          git checkout -B \"$PR_BRANCH\"\n          git push -u origin \"$PR_BRANCH\" --force\n\n          PR_TITLE=\"chore: update scaffolding to Kubebuilder ${VERSION}\"\n          PR_BODY=$'Automated update of Kubebuilder project scaffolding to '\"${VERSION}\"$'.\\n\\nMore info: https://github.com/kubernetes-sigs/kubebuilder/releases\\n\\n :warning: If conflicts arise, resolve them and run:\\n```bash\\nmake manifests generate fmt vet lint-fix\\n```'\n\n          # Try to create the PR; ignore error only if it already exists\n          if ! gh pr create \\\n            --title \"${PR_TITLE}\" \\\n            --body \"${PR_BODY}\" \\\n            --base main \\\n            --head \"$PR_BRANCH\"\n          then\n            EXISTING=\"$(gh pr list --state open --head \"$PR_BRANCH\" --json number --jq '.[0].number' || true)\"\n            if [ -n \"${EXISTING}\" ]; then\n              echo \"PR #${EXISTING} already exists for ${PR_BRANCH}, branch updated.\"\n            else\n              echo \"Failed to create PR for ${PR_BRANCH} and no open PR found.\"\n              exit 1\n            fi\n          fi\n```\n\n## Motivation\n\nA significant challenge faced by Kubebuilder users is keeping their projects up-to-date with the latest\nscaffolds while preserving customizations. The manual processes required for updates are time-consuming,\nerror-prone, and often discourage users from adopting new versions, leading to outdated and insecure projects.\n\nThe primary motivation for this proposal is to simplify and automate the process of maintaining Kubebuilder\nprojects. By providing a streamlined workflow for updates, this solution ensures that users can keep\ntheir projects aligned with modern standards while retaining their customizations.\n\n### Goals\n\n- **Automate Updates**: Detect and apply scaffold updates while preserving customizations.\n- **Simplify Updates**: Generate pull requests for easy review and merging.\n- **Provide Local Tooling**: Allow developers to run updates locally with preserved customizations.\n- **Keep Projects Current**: Ensure alignment with the latest scaffold improvements.\n- **Minimize Disruptions**: Enable scheduled or on-demand updates.\n\n### Non-Goals\n\n- **Automating conflict resolution for heavily customized projects**.\n- **Automatically merging updates without developer review**.\n- **Supporting monorepo project layouts or handling repositories that contain more than just the Kubebuilder-generated code**.\n\n## Proposal\n\n### User Stories\n\n- **As a Kubebuilder maintainer**, I want to help users keep their projects updated with minimal effort, ensuring they adhere to best practices and maintain alignment with project standards.\n- **As a user of Kubebuilder**, I want my project to stay up-to-date with the latest scaffold best practices while preserving customizations.\n- **As a user of Kubebuilder**, I want an easy way to apply updates across multiple repositories, saving time on manual updates.\n- **As a user of Kubebuilder**, I want to ensure my codebases remain secure and maintainable without excessive manual effort.\n\n### Implementation Details/Notes/Constraints\n\n- Introduce a new [Kubebuilder Plugin](https://book.kubebuilder.io/plugins/plugins) that scaffolds the\n  **GitHub Action** based on the POC. This plugin will be released as an **alpha feature**,\n  allowing users to opt-in for automated updates.\n\n- The plugin should be added by default in the Golang projects build with Kubebuilder, so new\n  projects can benefit from the automated updates without additional configuration. While it will not be escaffolded\n  by default in tools which extend Kubebuilder such as the Operator-SDK, where the alpha generate and update\n  features cannot be ported or extended.\n\n- Documentation should be provided to guide users on how to enable and use the new plugin as the new alpha command\n\n- The alpha command update should\n  - provide help and examples of usage\n  - allow users to specify the version of Kubebuilder they want to update to or from to\n  - allow users to specify the path of the project they want to update\n  - allow users to specify the output directory where the updated scaffold should be generated\n  - re-use the existing `kubebuilder alpha generate` command to generate the updated scaffold\n\n- The `kubebuilder alpha update` command should be covered with e2e tests to ensure it works as expected\n  and that the generated scaffold is valid and can be built.\n\n## Risks and Mitigations\n- **Risk**: Frequent conflicts may make the process cumbersome.\n    - *Mitigation*: Provide clear conflict summaries and leverage GitHub preview tools.\n- **Risk**: High maintenance overhead.\n    - *Mitigation*: Build a dedicated command-line tool (`kubebuilder alpha update`) to streamline updates and minimize complexity.\n\n## Proof of Concept\n\nThe feasibility of re-scaffolding projects has been demonstrated by the\n`kubebuilder alpha generate` command.\n\n**Command Example:**\n\n```bash\nkubebuilder alpha generate\n```\n\nFor more details, refer to the [Alpha Generate Documentation](https://kubebuilder.io/reference/rescaffold).\n\nThis command allows users to manually re-scaffold a project, to allow users add their code on top.\nIt confirms the technical capability of regenerating and updating scaffolds effectively.\n\nThis proposal builds upon this foundation by automating the process. The proposed tool would extend this functionality\nto automatically update projects with new scaffold versions, preserving customizations.\n\nThe three-way merge approach is a common strategy for integrating changes from multiple sources.\nIt is widely used in version control systems to combine changes from a common ancestor with two sets of modifications.\nIn the context of this proposal, the three-way merge would combine the original scaffold, the updated scaffold, and the user’s custom code\nseems to be very promising.\n\n### POC Implementation using 3-way merge:\n\nFollowing some POCs done to demonstrate the three-way merge approach\nwhere a project was escaffolded with Kubebuilder `v4.5.0` or `v4.5.2`\nand then updated to `v4.6.0`\n\n```shell\n## The following options were passed when merging UPGRADE:\n\ngit config --global merge.yaml.name \"Custom YAML merge\"\ngit config --global merge.yaml.driver \"yaml-merge %O %A %B\"\ngit config merge.conflictStyle diff3\ngit config rerere.enabled true\ngit config merge.renameLimit 999999\nHere are the steps taken:\n\n## On main:\n\ngit checkout -b ancestor\nClean up the ancestor and commit\n\nrm -fr *\ngit add .\ngit commit -m \"clean up ancestor\"\n\n## Bring back the PROJECT file, re-scaffold with v4.5.0, and commit\n\ngit checkout main -- PROJECT\nkubebuilder alpha generate\ngit add .\ngit commit -m \"alpha generate on ancestor with 4.5.0\"\n## Then proceed to create the original (ours) branch, bring back the code on main, add and commit:\n\ngit checkout -b original\ngit checkout main -- .\ngit add .\ngit commit -m \"add code back in original\"\n\n## Then create the upgrade branch (theirs), run kubebuilder alpha generate with v4.6.0 add and commit:\n\ngit checkout ancestor\ngit checkout -b upgrade\nkubebuilder alpha generate\ngit add .\ngit commit -m \"alpha generate on upgrade with 4.6.0\"\n\n## So now we have the ancestor, the original, and the upgrade branches all set, we can create a branch to commit the merge with the conflict markers:\n\ngit checkout original\ngit checkout -b merge\ngit merge upgrade\ngit add .\ngit commit -m \"Merge with upgrade with conflict markers\"\n## Now that we have performed the three way merge and commited the conflict markers, we can open a PR against main.\n```\n\nAs the script:\n\n```bash\n#!/bin/bash\n\nset -euo pipefail\n\n# CONFIG — change as needed\nREPO_PATH=\"$HOME/go/src/github/camilamacedo86/wordpress-operator\"\nKUBEBUILDER_SRC=\"$HOME/go/src/sigs.k8s.io/kubebuilder\"\nPROJECT_FILE=\"PROJECT\"\n\necho \"📦 Kubebuilder 3-way merge upgrade (v4.5.0 → v4.6.0)\"\necho \"📂 Working in: $REPO_PATH\"\necho \"🧪 Kubebuilder source: $KUBEBUILDER_SRC\"\n\ncd \"$REPO_PATH\"\n\n# Step 1: Create ancestor branch and clean it up\necho \"🌱 Creating 'ancestor' branch\"\ngit checkout -b ancestor main\n\necho \"🧼 Cleaning all files and folders (including dotfiles), except .git and PROJECT\"\nfind . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +\n\ngit add -A\ngit commit -m \"Clean ancestor branch\"\n\n# Step 2: Install Kubebuilder v4.5.0 and regenerate scaffold\necho \"⬇️ Installing Kubebuilder v4.5.0\"\ncd \"$KUBEBUILDER_SRC\"\ngit checkout upstream/release-4.5\nmake install\nkubebuilder version\n\ncd \"$REPO_PATH\"\necho \"📂 Restoring PROJECT file\"\ngit checkout main -- \"$PROJECT_FILE\"\nkubebuilder alpha generate\nmake manifests generate fmt vet lint-fix\ngit add -A\ngit commit -m \"alpha generate on ancestor with v4.5.0\"\n\n# Step 3: Create original branch with user's code\necho \"📦 Creating 'original' branch with user code\"\ngit checkout -b original\ngit checkout main -- .\ngit add -A\ngit commit -m \"Add project code into original\"\n\n# Step 4: Install Kubebuilder v4.6.0 and scaffold upgrade\necho \"⬆️ Installing Kubebuilder v4.6.0\"\ncd \"$KUBEBUILDER_SRC\"\ngit checkout upstream/release-4.6\nmake install\nkubebuilder version\n\ncd \"$REPO_PATH\"\necho \"🌿 Creating 'upgrade' branch from ancestor\"\ngit checkout ancestor\ngit checkout -b upgrade\necho \"🧼 Cleaning all files and folders (including dotfiles), except .git and PROJECT\"\nfind . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +\n\nkubebuilder alpha generate\nmake manifests generate fmt vet lint-fix\ngit add -A\ngit commit -m \"alpha generate on upgrade with v4.6.0\"\n\n# Step 5: Merge original into upgrade and preserve conflicts\necho \"🔀 Creating 'merge' branch from upgrade and merging original\"\ngit checkout upgrade\ngit checkout -b merge\n\n# Do a non-interactive merge and commit manually\necho \"🤖 Running non-interactive merge...\"\nset +e\ngit merge --no-edit --no-commit original\nMERGE_EXIT_CODE=$?\nset -e\n\n# Stage everything and commit with an appropriate message\nif [ $MERGE_EXIT_CODE -ne 0 ]; then\n  # Manually the alpha generate should out put the info so the person can fix it\n  echo \"⚠️ Conflicts occurred.\"\n  echo \"You will need to fix the conflicts manually and run the following commands:\"\n  echo \"make manifests generate fmt vet lint-fix\"\n  echo \"⚠️ Conflicts occurred. Keeping conflict markers and committing them.\"\n  git add -A\n  git commit -m \"upgrade has conflicts to be solved\"\nelse\n  echo \"Merge successful with no conflicts. Running commands\"\n  make manifests generate fmt vet lint-fix\n\n  echo \"✅ Merge successful with no conflicts.\"\n  git add -A\n  git commit -m \"upgrade worked without conflicts\"\nfi\n\necho \"\"\necho \"📍 You are now on the 'merge' branch.\"\necho \"📤 Push with: git push -u origin merge\"\necho \"🔁 Then open a PR to 'main' on GitHub.\"\necho \"\"\n```\n\n## Drawbacks\n\n- **Frequent Conflicts:** Automated updates may often result in conflicts, making the process cumbersome for users.\n- **Complex Resolutions:** If conflicts are hard to review and resolve, users may find the solution impractical.\n- **Maintenance Overhead:** The implementation could become too complex for maintainers to develop and support effectively.\n\n## Alternatives\n\n- **Manual Update Workflow**: Continue with manual updates where users regenerate\nand merge changes independently, though this is time-consuming and error-prone.\n- **Use alpha generate command**: Continue with partially automated updates provided\nby the alpha generate command.\n- **Dependabot Integration**: Leverage Dependabot for dependency updates, though this\ndoesn’t fully support scaffold updates and could lead to incomplete upgrades.\n"
  },
  {
    "path": "docs/CONTRIBUTING-ROLES.md",
    "content": "Contributing Roles\n==================\n\n## Direct Code-Related Roles\n\nWhile anyone (who's signed the [CLA and follows the code of\nconduct](../CONTRIBUTING.md)) is welcome to contribute to the Kubebuilder\nproject, we've got two \"formal\" roles that carry additional privileges and\nresponsibilities: *reviewer* and *approver*.\n\nIn a nutshell, reviewers and approvers are officially recognized to make\nday-to-day and overarching technical decisions within parts of the\nproject, or the project as a whole.  We follow a similar set of\ndefinitions to the [main Kubernetes project itself][kube-ladder], with\nslightly looser requirements.\n\nAs much as possible, we want people to help take on responsibility for the\nproject -- these guidelines are attempts to make it *easier* for this to\nhappen, *not harder*.  If you've got any questions, just reach out on\nSlack to one of the [subproject leads][kb-leads] (called\nkubebuilder-admins in the `OWNERS_ALIASES` file).\n\n## Prerequisite: Member\n\nAnyone who wants to become a reviewer or approver must first be a [member\nof the Kubernetes project][kube-member].  The aforementioned doc has more\ndetails, but the gist is that you must have made a couple of contributions to\nsome part of the Kubernetes project -- *this includes Kubebuilder and\nrelated repos*.  Then, you need two existing members to sponsor you.\n\n**If you've contributed a few times to Kubebuilder, we'll be happy to\nsponsor you, just ping us on Slack :-)**\n\n## Reviewers\n\nReviewers are recognized as able to provide code reviews for parts of the\ncodebase and are entered into the `reviewers` section of one or more\n`OWNERS` files.  You'll get auto-assigned reviews for your area of the\ncodebase and are generally expected to review for correctness,\ntesting, general code organization, etc.  Reviewers may review for design\nas well, but approvers have the final say on that.\n\nThings to look for:\n\n- does this code work, and is it written performantly and idiomatically?\n- is it tested?\n- is it organized nicely?  Is it maintainable?\n- is it documented?\n- does it need to be threadsafe?  Is it?\n- Take a glance at the stuff for approvers, if you can.\n\nReviewers' `/lgtm` marks are generally trusted by approvers to means that\nthe code is ready for one last look-over before merging.\n\n### Becoming a Reviewer\n\nThe criteria for becoming a reviewer are:\n\n- Give 5-10 reviews on PRs\n- Contribute or review 3-5 PRs substantially (i.e. take on the role of the\n  defacto \"main\" reviewer for the PR, contribute a bugfix or feature, etc)\n\nUsually, this will need to occur within a single repository, but if you've\nworked on a cross-cutting feature, it's ok to count PRs across\nrepositories.\n\nOnce you meet those criteria, submit yourself as a reviewer in the\n`OWNERS` file or files that you feel represent your areas of knowledge via\na PR to the relevant repository.\n\n## Approvers\n\nApprovers provide the final say as to whether a piece of code is merged.\nOnce approvals (`/approve`) are given for each piece of the affected code\n(and a reviewer or approver has added `/lgtm`), the code will merge.\n\nApprovers are responsible for giving the code a final once-over before\nmerge, and do an overall design/API review.\n\nThings to look for:\n\n- Does the API exposed to the user make sense, and is it easy to use?\n- Is it backward compatible?\n- Will it accommodate new changes in the future?\n- Is it extensible/layer-able (see [DESIGN.md](../DESIGN.md))?\n- Does it expose a new type from `k8s.io/XYZ`, and, if so, is it worth it?\n  Is that piece well-designed?\n\n**For large changes, approvers are responsible for getting reasonable\nconsensus**.  With the power to approve such changes comes the\nresponsibility of ensuring that the project as a whole has time to discuss\nthem.\n\n### Becoming an Approver\n\nAll approvers need to start as reviewers.  The criteria for becoming\nan approver is:\n\n- Be a reviewer in the area for a couple of months\n- Be the \"main\" reviewer or contributor for 5-10 substantial (bugfixes,\n  features, etc) PRs where approvers did not need to leave substantial\n  additional comments (i.e. where you were acting as a defacto approver).\n\nOnce you've met those criteria, you can submit yourself as an approver\nusing a PR that edits the relevant `OWNERS` files appropriately.  The\nexisting approvers will then approve the change with lazy consensus.  If\nyou feel more comfortable asking before submitting the PR, feel free to\nping one of the [subproject leads][kb-leads] (called kubebuilder-admins in\nthe `OWNERS_ALIASES` file) on Slack.\n\n## Indirectly Code-Related/Non-Code Roles\n\nWe're always looking for help with other areas of the project as well, such\nas:\n\n### Docs\n\nDocs contributors are always welcome.  Docs folks can also become\nreviewers/approvers for the book by following the same process above.\n\n### Triage\n\nHelp to triage our issues is also welcome.  Folks doing triage are\nresponsible for using the following commands to mark PRs and issues with\none or more labels, and should also feel free to help answer questions:\n\n- `/kind {bug|feature|documentation}`: things that are broken/new\n  things/things with lots of words, respectively\n\n- `/triage support`: questions, and things that might be bugs but might\n  just be confused about how to use something\n\n- `/priority {backlog|important-longterm|important-soon|critical-urgent}`:\n  how soon we need to deal with the thing (if someone wants\n  to/eventually/pretty soon/RIGHT NOW OMG THINGS ARE ON FIRE,\n  respectively)\n\n- `/good-first-issue`: this is pretty straightforward to implement, has\n  a clear plan, and clear criteria for being complete\n\n- `/help`: this could feasibly still be picked up by someone new-ish, but\n  has some wrinkles or nitty-gritty details that might not make it a good\n  first issue\n\nSee the [Prow reference](https://prow.k8s.io/command-help) for more\ndetails.\n\n[kube-ladder]: https://github.com/kubernetes/community/blob/master/community-membership.md \"Kubernetes Community Membership\"\n\n[kube-member]: https://github.com/kubernetes/community/blob/master/community-membership.md#member \"Kubernetes Project Member\"\n\n[kb-leads]: ../OWNERS_ALIASES \"Root OWNERS file -- kubebuilder-admins\"\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Running mdBook\n\nThe kubebuilder book is served using [mdBook](https://github.com/rust-lang-nursery/mdBook). If you want to test changes to the book locally, follow these directions:\n\n1. Follow the instructions at [https://rust-lang.github.io/mdBook/guide/installation.html](https://rust-lang.github.io/mdBook/guide/installation.html) to\n   install mdBook.\n2. Make sure [controller-gen](https://pkg.go.dev/sigs.k8s.io/controller-tools/cmd/controller-gen) is installed in `$GOPATH`.\n3. cd into the `docs/book` directory\n4. Run `mdbook serve`\n5. Visit [http://localhost:3000](http://localhost:3000)\n\n# Steps to deploy\n\nThere are no manual steps needed to deploy the website.\n\nKubebuilder book website is deployed on Netlify.\nThere is a preview of the website for each PR.\nAs soon as the PR is merged, the website will be built and deployed on Netlify.\n"
  },
  {
    "path": "docs/book/.firebaserc",
    "content": "{}\n"
  },
  {
    "path": "docs/book/book.toml",
    "content": "[book]\nauthors = [\"The Kubebuilder Maintainers\"]\nsrc = \"src\"\ntitle = \"The Kubebuilder Book\"\n\n[output.html]\ndefault-theme = \"light\"\npreferred-dark-theme = \"navy\"\nsmart-punctuation = true\nadditional-css = [\"theme/css/markers.css\", \"theme/css/custom.css\", \"theme/css/version-dropdown.css\"]\ngit-repository-url = \"https://github.com/kubernetes-sigs/kubebuilder\"\nedit-url-template = \"https://github.com/kubernetes-sigs/kubebuilder/edit/master/docs/book/{path}\"\nsidebar-header-nav = false\n\n[preprocessor.literatego]\ncommand = \"./litgo.sh\"\n\n[preprocessor.markerdocs]\ncommand = \"./markerdocs.sh\"\n"
  },
  {
    "path": "docs/book/functions/handle-version.js",
    "content": "function notFound(info) {\n    return {\n        statusCode: 404,\n        headers: {'content-type': 'text/html'},\n        body: (\"<h1>Not Found</h1>\"+\n            \"<p>You shouldn't see this page, please file a bug</p>\"+\n            `<details><summary>debug details</summary><pre><code>${JSON.stringify(info)}</code></pre></details>`\n        ),\n    };\n}\n\nfunction redirectToDownload(version, file) {\n    const loc = `https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${version}/${file}`;\n    return {\n        statusCode: 302,\n        headers: {'location': loc, 'content-type': 'text/plain'},\n        body: `Redirecting to ${loc}`,\n    };\n}\n\n\nexports.handler = async function(evt, ctx) {\n    // grab the prefix too to check for coherence\n    const [prefix, version, os, arch] = evt.path.split(\"/\").slice(-4);\n    if (prefix !== 'releases' || !version || !os || !arch) {\n        return notFound({version: version, os: os, arch: arch, prefix: prefix, rawPath: evt.path});\n    }\n\n    switch(version[0]) {\n        case '1':\n            // fallthrough\n        case '2':\n            return redirectToDownload(version, `kubebuilder_${version}_${os}_${arch}.tar.gz`);\n        default:\n            return redirectToDownload(version, `kubebuilder_${os}_${arch}`);\n    }\n}\n"
  },
  {
    "path": "docs/book/install-and-build.sh",
    "content": "#!/bin/bash\n\n#  Copyright 2020 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\nset -e\n\n# The following code is required to allow the preview works with an upper go version\n# More info : https://community.netlify.com/t/go-version-1-13/5680\n# Get the directory that this script file is in\nTHIS_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\n\ncd \"$THIS_DIR\"\n\nif [[ -n \"$(command -v gimme)\" ]]; then\n    GO_VERSION=${GO_VERSION:-stable}  # Use the provided GO_VERSION or default to 'stable'\n    eval \"$(gimme $GO_VERSION)\"\nfi\necho go version\nGOBIN=$THIS_DIR/functions go install ./...\n\nos=$(go env GOOS)\narch=$(go env GOARCH)\n\n# translate arch to rust's conventions (if we can)\nif [[ ${arch} == \"amd64\" ]]; then\n    arch=\"x86_64\"\nelif [[ ${arch} == \"arm64\" ]]; then\n    arch=\"aarch64\"\nfi\n\n# translate os to rust's conventions (if we can)\next=\"tar.gz\"\ncmd=\"tar -C /tmp -xzvf\"\ncase ${os} in\n    windows)\n        target=\"pc-windows-msvc\"\n        ext=\"zip\"\n        cmd=\"unzip -d /tmp\"\n        ;;\n    darwin)\n        target=\"apple-darwin\"\n        ;;\n    linux)\n        # works for linux, too\n        target=\"unknown-${os}-musl\"\n        ;;\n    *)\n        target=\"unknown-${os}\"\n        ;;\nesac\n\n# grab mdbook\nMDBOOK_VERSION=\"v0.5.2\"\nMDBOOK_BASENAME=\"mdBook-${MDBOOK_VERSION}-${arch}-${target}\"\nMDBOOK_URL=\"https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/${MDBOOK_BASENAME}.${ext}\"\n\necho \"downloading ${MDBOOK_BASENAME}.${ext} from ${MDBOOK_URL}\"\nset -x\ncurl -fL -o /tmp/mdbook.${ext} \"${MDBOOK_URL}\"\n${cmd} /tmp/mdbook.${ext}\nchmod +x /tmp/mdbook\n\nCONTROLLER_GEN_VERSION=\"v0.20.1\"\n\necho \"grabbing the controller-gen version: ${CONTROLLER_GEN_VERSION}\"\ngo version\ngo install sigs.k8s.io/controller-tools/cmd/controller-gen@${CONTROLLER_GEN_VERSION}\n\n# make sure we add the go bin directory to our path\ngobin=$(go env GOBIN)\ngobin=${gobin:-$(go env GOPATH)/bin} # GOBIN won't always be set :-/\n\nexport PATH=${gobin}:$PATH\nverb=${1:-build}\n/tmp/mdbook ${verb}\n"
  },
  {
    "path": "docs/book/litgo.sh",
    "content": "#!/bin/bash\n\n#  Copyright 2020 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\nset -ex\n\n(\n    pushd ./utils\n    go build -o ../../../bin/literate-go ./litgo\n    popd\n) &>/dev/null\n\n../../bin/literate-go \"$@\"\n"
  },
  {
    "path": "docs/book/markerdocs.sh",
    "content": "#!/bin/bash\n\n#  Copyright 2020 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\nset -ex\n\n(\n    pushd ./utils\n    go build -o ../../../bin/marker-docs ./markerdocs\n    popd\n) &>/dev/null\n\n../../bin/marker-docs \"$@\"\n"
  },
  {
    "path": "docs/book/src/SUMMARY.md",
    "content": "# Summary\n\n[Introduction](./introduction.md)\n\n[Architecture](./architecture.md)\n\n[Quick Start](./quick-start.md)\n\n[Getting Started](./getting-started.md)\n\n---\n\n- [Tutorial: Building CronJob](cronjob-tutorial/cronjob-tutorial.md)\n\n  - [What's in a basic project?](./cronjob-tutorial/basic-project.md)\n  - [Every journey needs a start, every program a main](./cronjob-tutorial/empty-main.md)\n  - [Groups and Versions and Kinds, oh my!](./cronjob-tutorial/gvks.md)\n  - [Adding a new API](./cronjob-tutorial/new-api.md)\n  - [Designing an API](./cronjob-tutorial/api-design.md)\n\n    - [A Brief Aside: What's the rest of this stuff?](./cronjob-tutorial/other-api-files.md)\n\n  - [What's in a controller?](./cronjob-tutorial/controller-overview.md)\n  - [Implementing a controller](./cronjob-tutorial/controller-implementation.md)\n\n    - [You said something about main?](./cronjob-tutorial/main-revisited.md)\n\n  - [Implementing defaulting/validating webhooks](./cronjob-tutorial/webhook-implementation.md)\n  - [Running and deploying the controller](./cronjob-tutorial/running.md)\n\n    - [Deploying cert-manager](./cronjob-tutorial/cert-manager.md)\n    - [Deploying webhooks](./cronjob-tutorial/running-webhook.md)\n\n  - [Writing tests](./cronjob-tutorial/writing-tests.md)\n\n- [Tutorial: Multi-Version API](./multiversion-tutorial/tutorial.md)\n\n  - [Changing things up](./multiversion-tutorial/api-changes.md)\n  - [Hubs, spokes, and other wheel metaphors](./multiversion-tutorial/conversion-concepts.md)\n  - [Implementing conversion](./multiversion-tutorial/conversion.md)\n\n    - [and setting up the webhooks](./multiversion-tutorial/webhooks.md)\n\n  - [Deployment and Testing](./multiversion-tutorial/deployment.md)\n\n---\n\n- [Migrations](./migrations.md)\n\n  - [Manual Migration Process](./migration/manual-process.md)\n    - [Using AI](./migration/ai-helpers.md)\n      - [Step 1: Reorganize Layout](./migration/reorganize-layout.md)\n      - [Step 2: Discovery Commands](./migration/discovery-commands.md)\n      - [Step 3: Port Code](./migration/port-code.md)\n  - [Single Group to Multi-Group](./migration/multi-group.md)\n  - [Cluster-Scoped to Namespace-Scoped](./migration/namespace-scoped.md)\n\n- [Alpha Commands](./reference/alpha_commands.md)\n\n  - [alpha generate](./reference/commands/alpha_generate.md)\n  - [alpha update](./reference/commands/alpha_update.md)\n\n---\n\n- [Reference](./reference/reference.md)\n\n  - [Generating CRDs](./reference/generating-crd.md)\n  - [Using Finalizers](./reference/using-finalizers.md)\n  - [Good Practices](./reference/good-practices.md)\n  - [Raising Events](./reference/raising-events.md)\n  - [Watching Resources](./reference/watching-resources.md)\n    - [Owned Resources](./reference/watching-resources/secondary-owned-resources.md)\n    - [Not Owned Resources](./reference/watching-resources/secondary-resources-not-owned.md)\n    - [Using Predicates](./reference/watching-resources/predicates-with-watch.md)\n  - [Kind for Dev & CI](reference/kind.md)\n  - [What's a webhook?](reference/webhook-overview.md)\n    - [Admission webhook](reference/admission-webhook.md)\n    - [Webhook bootstrap problem](reference/webhook-bootstrap-problem.md)\n  - [Markers for Config/Code Generation](./reference/markers.md)\n\n    - [CRD Generation](./reference/markers/crd.md)\n    - [CRD Validation](./reference/markers/crd-validation.md)\n    - [CRD Processing](./reference/markers/crd-processing.md)\n    - [Webhook](./reference/markers/webhook.md)\n    - [Object/DeepCopy](./reference/markers/object.md)\n    - [RBAC](./reference/markers/rbac.md)\n    - [Scaffold](./reference/markers/scaffold.md)\n\n  - [controller-gen CLI](./reference/controller-gen.md)\n  - [completion](./reference/completion.md)\n  - [Artifacts](./reference/artifacts.md)\n  - [Platform Support](./reference/platform.md)\n  - [Monitoring with Pprof](./reference/pprof-tutorial.md)\n\n  - [Manager and CRDs Scope](./reference/scopes.md)\n    - [Manager Scope](./reference/manager-scope.md)\n    - [CRD Scope](./reference/crd-scope.md)\n\n  - [Sub-Module Layouts](./reference/submodule-layouts.md)\n  - [Using an external Resource / API](./reference/using_an_external_resource.md)\n\n  - [Configuring EnvTest](./reference/envtest.md)\n\n  - [Metrics](./reference/metrics.md)\n\n    - [Reference](./reference/metrics-reference.md)\n\n  - [Project config](./reference/project-config.md)\n  - [Versions Compatibility and Supportability](./versions_compatibility_supportability.md)\n\n---\n\n- [Plugins][plugins]\n\n  - [Available Plugins](./plugins/available-plugins.md)\n    - [autoupdate/v1-alpha](./plugins/available/autoupdate-v1-alpha.md)\n    - [deploy-image/v1-alpha](./plugins/available/deploy-image-plugin-v1-alpha.md)\n    - [go/v4](./plugins/available/go-v4-plugin.md)\n    - [grafana/v1-alpha](./plugins/available/grafana-v1-alpha.md)\n    - [helm/v1-alpha](./plugins/available/helm-v1-alpha.md)\n    - [helm/v2-alpha](./plugins/available/helm-v2-alpha.md)\n    - [kustomize/v2](./plugins/available/kustomize-v2.md)\n  - [Extending](./plugins/extending.md)\n    - [CLI and Plugins](./plugins/extending/extending_cli_features_and_plugins.md)\n    - [External Plugins](./plugins/extending/external-plugins.md)\n    - [Custom Markers](./plugins/extending/custom-markers.md)\n    - [E2E Tests](./plugins/extending/testing-plugins.md)\n  - [Plugins Versioning](./plugins/plugins-versioning.md)\n\n\n---\n\n[FAQ](./faq.md)\n\n[plugins]: ./plugins/plugins.md\n"
  },
  {
    "path": "docs/book/src/TODO.md",
    "content": "# Page Not Found\n\nThe page you are looking for could not be found. This might be because:\n\n1. The page has been moved or renamed\n2. The page is no longer available\n3. The URL was entered incorrectly\n\nPlease try:\n\n- Going back to the [home page](https://book.kubebuilder.io/)\n- Using the search function\n- Suggest an edit [documentation index](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src)\n\nCheck out if someone is working on your issue [report an issue](https://github.com/kubernetes-sigs/kubebuilder/issues)\nIf you believe this is an error, please [report an issue](https://github.com/kubernetes-sigs/kubebuilder/issues/new?template=BLANK_ISSUE)\nReach out to us on [Slack](https://kubernetes.slack.com/messages/kubebuilder)"
  },
  {
    "path": "docs/book/src/architecture.md",
    "content": "# Architecture Concept Diagram\n\nThe following diagram will help you get a better idea over the Kubebuilder concepts and architecture.\n\n<!-- include these inline so we can style an match variables -->\n{{#include ./kb_concept_diagram.svg}}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/api-design.md",
    "content": "# Designing an API\n\nIn Kubernetes, we have a few rules for how we design APIs. Namely, all\nserialized fields *must* be `camelCase`, so we use JSON struct tags to\nspecify this.  We can also use the `omitempty` struct tag to mark that\na field should be omitted from serialization when empty.\n\nFields may use most of the primitive types.  Numbers are the exception:\nfor API compatibility purposes, we accept three forms of numbers: `int32`\nand `int64` for integers, and `resource.Quantity` for decimals.\n\n<details><summary>Hold up, what's a Quantity?</summary>\n\nQuantities are a special notation for decimal numbers that have an\nexplicitly fixed representation that makes them more portable across\nmachines.  You've probably noticed them when specifying resources requests\nand limits on pods in Kubernetes.\n\nThey conceptually work similar to floating point numbers: they have\na significant, base, and exponent. Their serializable and human readable format\nuses whole numbers and suffixes to specify values much the way we describe\ncomputer storage.\n\nFor instance, the value `2m` means `0.002` in decimal notation.  `2Ki`\nmeans `2048` in decimal, while `2K` means `2000` in decimal.  If we want\nto specify fractions, we switch to a suffix that lets us use a whole\nnumber: `2.5` is `2500m`.\n\nThere are two supported bases: 10 and 2 (called decimal and binary,\nrespectively).  Decimal base is indicated with \"normal\" SI suffixes (e.g.\n`M` and `K`), while Binary base is specified in \"mebi\" notation (e.g. `Mi`\nand `Ki`).  Think [megabytes vs\nmebibytes](https://en.wikipedia.org/wiki/Binary_prefix).\n\n</details>\n\nThere's one other special type that we use: `metav1.Time`.  This functions\nidentically to `time.Time`, except that it has a fixed, portable\nserialization format.\n\nWith that out of the way, let's take a look at what our CronJob object\nlooks like!\n\n{{#literatego ./testdata/project/api/v1/cronjob_types.go}}\n\nNow that we have an API, we'll need to write a controller to actually\nimplement the functionality.\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/basic-project.md",
    "content": "# What's in a basic project?\n\nWhen scaffolding out a new project, Kubebuilder provides us with a few\nbasic pieces of boilerplate.\n\n## Build Infrastructure\n\nFirst up, basic infrastructure for building your project:\n\n<details><summary><code>go.mod</code>: A new Go module matching our project, with\nbasic dependencies</summary>\n\n```go\n{{#include ./testdata/project/go.mod}}\n```\n</details>\n\n<details><summary><code>Makefile</code>: Make targets for building and deploying your controller</summary>\n\n```makefile\n{{#include ./testdata/project/Makefile}}\n```\n</details>\n\n<details><summary><code>PROJECT</code>: Kubebuilder metadata for scaffolding new components</summary>\n\n```yaml\n{{#include ./testdata/project/PROJECT}}\n```\n</details>\n\n## Launch Configuration\n\nWe also get launch configurations under the\n[`config/`](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project/config)\ndirectory.  Right now, it just contains\n[Kustomize](https://sigs.k8s.io/kustomize) YAML definitions required to\nlaunch our controller on a cluster, but once we get started writing our\ncontroller, it'll also hold our CustomResourceDefinitions, RBAC\nconfiguration, and WebhookConfigurations.\n\n[`config/default`](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project/config/default) contains a [Kustomize base](https://github.com/kubernetes-sigs/kubebuilder/blob/master/docs/book/src/cronjob-tutorial/testdata/project/config/default/kustomization.yaml) for launching\nthe controller in a standard configuration.\n\nEach other directory contains a different piece of configuration,\nrefactored out into its own base:\n\n- [`config/manager`](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project/config/manager): launch your controllers as pods in the\n  cluster\n\n- [`config/rbac`](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project/config/rbac): permissions required to run your\n  controllers under their own service account\n\n## The Entrypoint\n\nLast, but certainly not least, Kubebuilder scaffolds out the basic\nentrypoint of our project: `main.go`.  Let's take a look at that next...\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/cert-manager.md",
    "content": "# Deploying cert-manager\n\nWe suggest using [cert-manager](https://github.com/cert-manager/cert-manager) for\nprovisioning the certificates for the webhook server. Other solutions should\nalso work as long as they put the certificates in the desired location.\n\nYou can follow\n[the cert-manager documentation](https://cert-manager.io/docs/installation/)\nto install it.\n\ncert-manager also has a component called [CA\nInjector](https://cert-manager.io/docs/concepts/ca-injector/), which is responsible for\ninjecting the CA bundle into the [`MutatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#MutatingWebhookConfiguration)\n/ [`ValidatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#ValidatingWebhookConfiguration).\n\nTo accomplish that, you need to use an annotation with key\n`cert-manager.io/inject-ca-from`\nin the [`MutatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#MutatingWebhookConfiguration)\n/ [`ValidatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#ValidatingWebhookConfiguration) objects.\nThe value of the annotation should point to an existing [certificate request instance](https://cert-manager.io/docs/concepts/certificaterequest/)\nin the format of `<certificate-namespace>/<certificate-name>`.\n\nThis is the [kustomize](https://github.com/kubernetes-sigs/kustomize) patch we\nused for annotating the [`MutatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#MutatingWebhookConfiguration)\n/ [`ValidatingWebhookConfiguration`](https://pkg.go.dev/k8s.io/api/admissionregistration/v1#ValidatingWebhookConfiguration) objects.\n\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/controller-implementation.md",
    "content": "# Implementing a controller\n\nThe basic logic of our CronJob controller is this:\n\n1. Load the named CronJob\n\n2. List all active jobs, and update the status\n\n3. Clean up old jobs according to the history limits\n\n4. Check if we're suspended (and don't do anything else if we are)\n\n5. Get the next scheduled run\n\n6. Run a new job if it's on schedule, not past the deadline, and not\n   blocked by our concurrency policy\n\n7. Requeue when we either see a running job (done automatically) or it's\n   time for the next scheduled run.\n\n{{#literatego ./testdata/project/internal/controller/cronjob_controller.go}}\n\nThat was a doozy, but now we've got a working controller.  Let's test\nagainst the cluster, then, if we don't have any issues, deploy it!\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/controller-overview.md",
    "content": "# What's in a controller?\n\nControllers are the core of Kubernetes, and of any operator.\n\nIt's a controller's job to ensure that, for any given object, the actual\nstate of the world (both the cluster state, and potentially external state\nlike running containers for Kubelet or loadbalancers for a cloud provider)\nmatches the desired state in the object.  Each controller focuses on one\n*root* Kind, but may interact with other Kinds.\n\nWe call this process *reconciling*.\n\nIn controller-runtime, the logic that implements the reconciling for\na specific kind is called a [*Reconciler*](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile?tab=doc).  A reconciler\ntakes the name of an object, and returns whether or not we need to try\nagain (e.g. in case of errors or periodic controllers, like the\nHorizontalPodAutoscaler).\n\n{{#literatego ./testdata/emptycontroller.go}}\n\nNow that we've seen the basic structure of a reconciler, let's fill out\nthe logic for `CronJob`s.\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/cronjob-tutorial.md",
    "content": "# Tutorial: Building CronJob\n\nToo many tutorials start out with some really contrived setup, or some toy\napplication that gets the basics across, and then stalls out on the more\ncomplicated stuff.  Instead, this tutorial should take you through (almost)\nthe full gamut of complexity with Kubebuilder, starting off simple and\nbuilding up to something pretty full-featured.\n\nLet's pretend (and sure, this is a teensy bit contrived) that we've\nfinally gotten tired of the maintenance burden of the non-Kubebuilder\nimplementation of the CronJob controller in Kubernetes, and we'd like to\nrewrite it using Kubebuilder.\n\nThe job (no pun intended) of the *CronJob* controller is to run one-off\ntasks on the Kubernetes cluster at regular intervals.  It does this by\nbuilding on top of the *Job* controller, whose task is to run one-off tasks\nonce, seeing them to completion.\n\nInstead of trying to tackle rewriting the Job controller as well, we'll\nuse this as an opportunity to see how to interact with external types.\n\n<aside class=\"note\">\n\n<h1>Following Along vs Jumping Ahead</h1>\n\nNote that most of this tutorial is generated from literate Go files that\nlive in the book source directory:\n[docs/book/src/cronjob-tutorial/testdata][tutorial-source].  The full,\nrunnable project lives in [project][tutorial-project-source], while\nintermediate files live directly under the [testdata][tutorial-source]\ndirectory.\n\n[tutorial-source]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata\n\n[tutorial-project-source]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project\n\n</aside>\n\n## Scaffolding Out Our Project\n\nAs covered in the [quick start](../quick-start.md), we'll need to scaffold\nout a new project.  Make sure you've [installed\nKubebuilder](../quick-start.md#installation), then scaffold out a new\nproject:\n\n```bash\n# create a project directory, and then run the init command.\nmkdir project\ncd project\n# we'll use a domain of tutorial.kubebuilder.io,\n# so all API groups will be <group>.tutorial.kubebuilder.io.\nkubebuilder init --domain tutorial.kubebuilder.io --repo tutorial.kubebuilder.io/project\n```\n\n<aside class=\"note\">\n\nYour project's name defaults to that of your current working directory.\nYou can pass `--project-name=<dns1123-label-string>` to set a different project name.\n\n</aside>\n\nNow that we've got a project in place, let's take a look at what\nKubebuilder has scaffolded for us so far...\n\n<aside class=\"note\">\n\n<h1>Developing in <code>$GOPATH</code></h1>\n\nIf your project is initialized within [`GOPATH`][GOPATH-golang-docs], the implicitly called `go mod init` will interpolate the module path for you.\nOtherwise `--repo=<module path>` must be set.\n\nRead the [Go modules blogpost][go-modules-blogpost] if unfamiliar with the module system.\n\n</aside>\n\n[GOPATH-golang-docs]: https://golang.org/doc/code.html#GOPATH\n[go-modules-blogpost]: https://blog.golang.org/using-go-modules"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/empty-main.md",
    "content": "# Every journey needs a start, every program needs a main\n\n{{#literatego ./testdata/emptymain.go}}\n\nWith that out of the way, we can get on to scaffolding our API!\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/gvks.md",
    "content": "# Groups and Versions and Kinds, oh my!\n\nBefore we get started with our API, we should talk about terminology\na bit.\n\nWhen we talk about APIs in Kubernetes, we often use 4 terms: *groups*,\n*versions*, *kinds*, and *resources*.\n\n## Groups and Versions\n\nAn *API Group* in Kubernetes is simply a collection of related\nfunctionality.  Each group has one or more *versions*, which, as the name\nsuggests, allow us to change how an API works over time.\n\n## Kinds and Resources\n\nEach API group-version contains one or more API types, which we call\n*Kinds*.  While a Kind may change forms between versions, each form must\nbe able to store all the data of the other forms, somehow (we can store\nthe data in fields, or in annotations).  This means that using an older\nAPI version won't cause newer data to be lost or corrupted.  See the\n[Kubernetes API\nguidelines](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md)\nfor more information.\n\nYou'll also hear mention of *resources* on occasion.  A resource is simply\na use of a Kind in the API.  Often, there's a one-to-one mapping between\nKinds and resources.  For instance, the `pods` resource corresponds to the\n`Pod` Kind.  However, sometimes, the same Kind may be returned by multiple\nresources.  For instance, the `Scale` Kind is returned by all scale\nsubresources, like `deployments/scale` or `replicasets/scale`.  This is\nwhat allows the Kubernetes HorizontalPodAutoscaler to interact with\ndifferent resources.  With CRDs, however, each Kind will correspond to\na single resource.\n\nNotice that resources are always lowercase, and by convention are the\nlowercase form of the Kind.\n\n## So, how does that correspond to Go?\n\nWhen we refer to a kind in a particular group version, we'll call it\na *GroupVersionKind*, or GVK for short.  Same with resources and GVR. As\nwe'll see shortly, each GVK corresponds to a given root Go type in\na package.\n\nNow that we have our terminology straight, we can *actually* create our\nAPI!\n\n## So, how can we create our API?\n\nIn the next section, [Adding a new API](../cronjob-tutorial/new-api.html), we will check how the tool helps us to\ncreate our own APIs with the command `kubebuilder create api`.\n\nThe goal of this command is to create a Custom Resource (CR) and Custom Resource Definition (CRD) for our Kind(s). To check it further see; [Extend the Kubernetes API with CustomResourceDefinitions][kubernetes-extend-api].\n\n## But, why create APIs at all?\n\nNew APIs are how we teach Kubernetes about our custom objects. The Go structs are used to generate a CRD which includes the schema for our data as well as tracking data like what our new type is called. We can then create instances of our custom objects which will be managed by our [controllers][controllers].\n\nOur APIs and resources represent our solutions on the clusters. Basically, the CRDs are a definition of our customized Objects, and the CRs are an instance of it.\n\n## Ah, do you have an example?\n\nLet’s think about the classic scenario where the goal is to have an application and its database running on the platform with Kubernetes. Then, one CRD could represent the App, and another one could represent the DB. By having one CRD to describe the App and another one for the DB, we will not be hurting concepts such as encapsulation, the single responsibility principle, and cohesion. Damaging these concepts could cause unexpected side effects, such as difficulty in extending, reuse, or maintenance, just to mention a few.\n\nIn this way, we can create the App CRD which will have its controller and which would be responsible for things like creating Deployments that contain the App and creating Services to access it and etc. Similarly, we could create a CRD to represent the DB, and deploy a controller that would manage DB instances.\n\n## Err, but what's that Scheme thing?\n\nThe `Scheme` we saw before is simply a way to keep track of what Go type\ncorresponds to a given GVK (don't be overwhelmed by its\n[godocs](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime?tab=doc#Scheme)).\n\nFor instance, suppose we mark the\n`\"tutorial.kubebuilder.io/api/v1\".CronJob{}` type as being in the\n`batch.tutorial.kubebuilder.io/v1` API group (implicitly saying it has the\nKind `CronJob`).\n\nThen, we can later construct a new `&CronJob{}` given some JSON from the\nAPI server that says\n\n```json\n{\n    \"kind\": \"CronJob\",\n    \"apiVersion\": \"batch.tutorial.kubebuilder.io/v1\",\n    ...\n}\n```\n\nor properly look up the group version when we go to submit a `&CronJob{}`\nin an update.\n\n[kubernetes-extend-api]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/\n[controllers]: ../cronjob-tutorial/controller-overview.md\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/main-revisited.md",
    "content": "# You said something about main?\n\nBut first, remember how we said we'd [come back to `main.go`\nagain](/cronjob-tutorial/empty-main.md)? Let's take a look and see what's\nchanged, and what we need to add.\n\n{{#literatego ./testdata/project/cmd/main.go}}\n\n*Now* we can implement our controller.\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/new-api.md",
    "content": "# Adding a new API\n\nTo scaffold out a new Kind (you were paying attention to the [last\nchapter](./gvks.md#kinds-and-resources), right?) and corresponding\ncontroller, we can use `kubebuilder create api`:\n\n```bash\nkubebuilder create api --group batch --version v1 --kind CronJob\n```\n\nPress `y` for \"Create Resource\" and \"Create Controller\".\n\nThe first time we call this command for each group-version, it will create\na directory for the new group-version.\n\nIn this case, the\n[`api/v1/`](https://sigs.k8s.io/kubebuilder/docs/book/src/cronjob-tutorial/testdata/project/api/v1)\ndirectory is created, corresponding to the\n`batch.tutorial.kubebuilder.io/v1` (remember our [`--domain`\nsetting](cronjob-tutorial.md#scaffolding-out-our-project) from the\nbeginning?).\n\nIt has also added a file for our `CronJob` Kind,\n`api/v1/cronjob_types.go`.  Each time we call the command with a different\nkind, it'll add a corresponding new file.\n\nLet's take a look at what we've been given out of the box, then we can\nmove on to filling it out.\n\n{{#literatego ./testdata/emptyapi.go}}\n\nNow that we've seen the basic structure, let's fill it out!\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/other-api-files.md",
    "content": "# A Brief Aside: What's the rest of this stuff?\n\nIf you've taken a peek at the rest of the files in the\n[`api/v1/`](https://sigs.k8s.io/kubebuilder/docs/book/src/cronjob-tutorial/testdata/project/api/v1)\ndirectory, you might have noticed two additional files beyond\n`cronjob_types.go`: `groupversion_info.go` and `zz_generated.deepcopy.go`.\n\nNeither of these files ever needs to be edited (the former stays the same\nand the latter is autogenerated), but it's useful to know what's in them.\n\n## `groupversion_info.go`\n\n`groupversion_info.go` contains common metadata about the group-version:\n\n{{#literatego ./testdata/project/api/v1/groupversion_info.go}}\n\n## `zz_generated.deepcopy.go`\n\n`zz_generated.deepcopy.go` contains the autogenerated implementation of\nthe aforementioned `runtime.Object` interface, which marks all of our root\ntypes as representing Kinds.\n\nThe core of the `runtime.Object` interface is a deep-copy method,\n`DeepCopyObject`.\n\nThe `object` generator in controller-tools also generates two other handy\nmethods for each root type and all its sub-types: `DeepCopy` and\n`DeepCopyInto`.\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/running-webhook.md",
    "content": "# Deploying Admission Webhooks\n\n## cert-manager\n\nYou need to follow [this](./cert-manager.md) to install the cert-manager bundle.\n\n## Build your image\n\nRun the following command to build your image locally.\n\n```bash\nmake docker-build docker-push IMG=<some-registry>/<project-name>:tag\n```\n\n<aside class=\"note\">\n<h1> Using Kind </h1>\n\nConsider incorporating Kind into your workflow for a faster, more efficient local development and CI experience.\nNote that, if you're using a Kind cluster, there's no need to push your image to a remote container registry.\nYou can directly load your local image into your specified Kind cluster:\n\n```bash\nkind load docker-image <your-image-name>:tag --name <your-kind-cluster-name>\n```\n\nTo know more, see: [Using Kind For Development Purposes and CI](./../reference/kind.md)\n\n</aside>\n\n\n## Deploy Webhooks\n\nYou need to enable the webhook and cert manager configuration through kustomize.\n`config/default/kustomization.yaml` should have the following webhook-related sections uncommented:\n\n**Resources** - Add the webhook and cert-manager resources:\n```yaml\n{{#include ./testdata/project/config/default/kustomization.yaml:webhook-resources}}\n```\n\n**Patches** - Add the webhook manager patch:\n```yaml\n{{#include ./testdata/project/config/default/kustomization.yaml:webhook-patch}}\n```\n\n**Replacements** - Add the webhook certificate replacements:\n```yaml\n{{#include ./testdata/project/config/default/kustomization.yaml:webhook-replacements}}\n```\n\nAnd `config/crd/kustomization.yaml` should now look like the following:\n\n```yaml\n{{#include ./testdata/project/config/crd/kustomization.yaml}}\n```\n\nNow you can deploy it to your cluster by\n\n```bash\nmake deploy IMG=<some-registry>/<project-name>:tag\n```\n\nWait a while till the webhook pod comes up and the certificates are provisioned.\nIt usually completes within 1 minute.\n\nNow you can create a valid CronJob to test your webhooks. The creation should\nsuccessfully go through.\n\n```bash\nkubectl create -f config/samples/batch_v1_cronjob.yaml\n```\n\nYou can also try to create an invalid CronJob (e.g. use an ill-formatted\nschedule field). You should see a creation failure with a validation error.\n\n<aside class=\"warning\">\n<h3>The Bootstrapping Problem</h3>\n\nWhen you deploy a webhook into the same cluster that it will validate, you can run into a *bootstrapping issue*:\nthe webhook may try to validate the creation of its own Pod before it’s actually running.\nThis can block the webhook from ever starting.\n\nTo avoid this, make sure the webhook **ignores its own resources**.\nYou can do this in one of two ways:\n\n- **[namespaceSelector]** – label the namespace where the webhook runs and configure the webhook to skip it.\n- **[objectSelector]** – label the webhook’s own Pods or Deployments and exclude those objects directly.\n\nSee the complete step-by-step guide: **[Webhook Bootstrap Problem](../reference/webhook-bootstrap-problem.md)**\n\n</aside>\n\n[namespaceSelector]: https://github.com/kubernetes/api/blob/kubernetes-1.14.5/admissionregistration/v1beta1/types.go#L189-L233\n[objectSelector]: https://github.com/kubernetes/api/blob/kubernetes-1.15.2/admissionregistration/v1beta1/types.go#L262-L274\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/running.md",
    "content": "# Running and deploying the controller\n\n### Optional\nIf opting to make any changes to the API definitions, then before proceeding,\ngenerate the manifests like CRs or CRDs with\n```bash\nmake manifests\n```\n\nTo test out the controller, we can run it locally against the cluster.\nBefore we do so, though, we'll need to install our CRDs, as per the [quick\nstart](/quick-start.md).  This will automatically update the YAML\nmanifests using controller-tools, if needed:\n\n```bash\nmake install\n```\n\n<aside class=\"note\">\n\n<h1>Too long annotations error</h1>\n\nIf you encounter errors when applying the CRDs, due to `metadata.annotations` exceeding the\n262144 bytes limit, please refer to the specific entry in the [FAQ section](/faq#the-error-too-long-must-have-at-most-262144-bytes-is-faced-when-i-run-make-install-to-apply-the-crd-manifests-how-to-solve-it-why-this-error-is-faced).\n\n</aside>\n\nNow that we've installed our CRDs, we can run the controller against our\ncluster.  This will use whatever credentials that we connect to the\ncluster with, so we don't need to worry about RBAC just yet.\n\n<aside class=\"note\">\n\n<h1>Running webhooks locally</h1>\n\nIf you want to run the webhooks locally, you'll have to generate\ncertificates for serving the webhooks, and place them in the right\ndirectory (`/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}`, by\ndefault).\n\nIf you're not running a local API server, you'll also need to figure out\nhow to proxy traffic from the remote cluster to your local webhook server.\nFor this reason, we generally recommend disabling webhooks when doing\nyour local code-run-test cycle, as we do below.\n\n</aside>\n\nIn a separate terminal, run\n\n```bash\nexport ENABLE_WEBHOOKS=false\nmake run\n```\n\nYou should see logs from the controller about starting up, but it won't do\nanything just yet.\n\nAt this point, we need a CronJob to test with.  Let's write a sample to\n`config/samples/batch_v1_cronjob.yaml`, and use that:\n\n```yaml\n{{#include ./testdata/project/config/samples/batch_v1_cronjob.yaml}}\n```\n\n```bash\nkubectl create -f config/samples/batch_v1_cronjob.yaml\n```\n\nAt this point, you should see a flurry of activity.  If you watch the\nchanges, you should see your cronjob running, and updating status:\n\n```bash\nkubectl get cronjob.batch.tutorial.kubebuilder.io -o yaml\nkubectl get job\n```\n\nNow that we know it's working, we can run it in the cluster. Stop the\n`make run` invocation, and run\n\n```bash\nmake docker-build docker-push IMG=<some-registry>/<project-name>:tag\nmake deploy IMG=<some-registry>/<project-name>:tag\n```\n\n<aside class=\"note\">\n<h1>Registry Permission</h1>\n\nThis image ought to be published in the personal registry you specified. And it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don't work.\n\nConsider incorporating Kind into your workflow for a faster, more efficient local development and CI experience.\nNote that, if you're using a Kind cluster, there's no need to push your image to a remote container registry.\nYou can directly load your local image into your specified Kind cluster:\n\n```bash\nkind load docker-image <your-image-name>:tag --name <your-kind-cluster-name>\n```\n\nTo know more, see: [Using Kind For Development Purposes and CI](./../reference/kind.md)\n\n<h1>RBAC errors</h1>\n\nIf you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin. See [Prerequisites for using Kubernetes RBAC on GKE cluster v1.11.x and older][pre-rbc-gke] which may be your case.\n\n</aside>\n\nIf we list cronjobs again like we did before, we should see the controller\nfunctioning again!\n\n[pre-rbc-gke]: https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control#iam-rolebinding-bootstrap"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/emptyapi.go",
    "content": "/*\nCopyright 2022.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWe start out simply enough: we import the `meta/v1` API group, which is not\nnormally exposed by itself, but instead contains metadata common to all\nKubernetes Kinds.\n*/\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n/*\nNext, we define types for the Spec and Status of our Kind.  Kubernetes functions\nby reconciling desired state (`Spec`) with actual cluster state (other objects'\n`Status`) and external state, and then recording what it observed (`Status`).\nThus, every *functional* object includes spec and status.  A few types, like\n`ConfigMap` don't follow this pattern, since they don't encode desired state,\nbut most types do.\n*/\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// CronJobSpec defines the desired state of CronJob\ntype CronJobSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n}\n\n// CronJobStatus defines the observed state of CronJob\ntype CronJobStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n}\n\n/*\nNext, we define the types corresponding to actual Kinds, `CronJob` and `CronJobList`.\n`CronJob` is our root type, and describes the `CronJob` kind.  Like all Kubernetes objects, it contains\n`TypeMeta` (which describes API version and Kind), and also contains `ObjectMeta`, which holds things\nlike name, namespace, and labels.\n\n`CronJobList` is simply a container for multiple `CronJob`s.  It's the Kind used in bulk operations,\nlike LIST.\n\nIn general, we never modify either of these -- all modifications go in either Spec or Status.\n\nThat little `+kubebuilder:object:root` comment is called a marker.  We'll see\nmore of them in a bit, but know that they act as extra metadata, telling\n[controller-tools](https://github.com/kubernetes-sigs/controller-tools) (our code and YAML generator) extra information.\nThis particular one tells the `object` generator that this type represents\na Kind.  Then, the `object` generator generates an implementation of the\n[runtime.Object](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime?tab=doc#Object) interface for us, which is the standard\ninterface that all types representing Kinds must implement.\n*/\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// CronJob is the Schema for the cronjobs API\ntype CronJob struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   CronJobSpec   `json:\"spec,omitempty\"`\n\tStatus CronJobStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob\ntype CronJobList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []CronJob `json:\"items\"`\n}\n\n/*\nFinally, we add the Go types to the API group.  This allows us to add the\ntypes in this API group to any [Scheme](https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime?tab=doc#Scheme).\n*/\nfunc init() {\n\tSchemeBuilder.Register(&CronJob{}, &CronJobList{})\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/emptycontroller.go",
    "content": "/*\nCopyright 2022.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nFirst, we start out with some standard imports.\nAs before, we need the core controller-runtime library, as well as\nthe client package, and the package for our API types.\n*/\npackage controllers\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n/*\nNext, kubebuilder has scaffolded a basic reconciler struct for us.\nPretty much every reconciler needs to log, and needs to be able to fetch\nobjects, so these are added out of the box.\n*/\n\n// CronJobReconciler reconciles a CronJob object\ntype CronJobReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n/*\nMost controllers eventually end up running on the cluster, so they need RBAC\npermissions, which we specify using controller-tools [RBAC\nmarkers](/reference/markers/rbac.md).  These are the bare minimum permissions\nneeded to run.  As we add more functionality, we'll need to revisit these.\n*/\n\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch\n\n/*\nThe `ClusterRole` manifest at `config/rbac/role.yaml` is generated from the above markers via controller-gen with the following command:\n*/\n\n// make manifests\n\n/*\nNOTE: If you receive an error, please run the specified command in the error and re-run `make manifests`.\n*/\n\n/*\n`Reconcile` actually performs the reconciling for a single named object.\nOur [Request](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile?tab=doc#Request) just has a name, but we can use the client to fetch\nthat object from the cache.\n\nWe return an empty result and no error, which indicates to controller-runtime that\nwe've successfully reconciled this object and don't need to try again until there's\nsome changes.\n\nMost controllers need a logging handle and a context, so we set them up here.\n\nThe [context](https://golang.org/pkg/context/) is used to allow cancellation of\nrequests, and potentially things like tracing.  It's the first argument to all\nclient methods.  The `Background` context is just a basic context without any\nextra data or timing restrictions.\n\nThe logging handle lets us log.  controller-runtime uses structured logging through a\nlibrary called [logr](https://github.com/go-logr/logr).  As we'll see shortly,\nlogging works by attaching key-value pairs to a static message.  We can pre-assign\nsome pairs at the top of our reconcile method to have those attached to all log\nlines in this reconciler.\n*/\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n/*\nFinally, we add this reconciler to the manager, so that it gets started\nwhen the manager is started.\n\nFor now, we just note that this reconciler operates on `CronJob`s.  Later,\nwe'll use this to mark that we care about related objects as well.\n\n*/\n\nfunc (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&batchv1.CronJob{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/emptymain.go",
    "content": "/*\nCopyright 2022 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\n\nOur package starts out with some basic imports.  Particularly:\n\n- The core [controller-runtime](https://pkg.go.dev/sigs.k8s.io/controller-runtime?tab=doc) library\n- The default controller-runtime logging, [Zap](https://pkg.go.dev/go.uber.org/zap) (more on that a bit later)\n\n*/\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/gcp\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\t// +kubebuilder:scaffold:imports\n)\n\n/*\nEvery set of controllers needs a\n[*Scheme*](https://book.kubebuilder.io/cronjob-tutorial/gvks.html#err-but-whats-that-scheme-thing),\nwhich provides mappings between Kinds and their corresponding Go types.  We'll\ntalk a bit more about Kinds when we write our API definition, so just keep this\nin mind for later.\n*/\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\t// +kubebuilder:scaffold:scheme\n}\n\n/*\nAt this point, our main function is fairly simple:\n\n- We set up some basic flags for metrics.\n\n- We instantiate a\n[*manager*](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager?tab=doc#Manager),\nwhich keeps track of running all of our controllers, as well as setting up\nshared caches and clients to the API server (notice we tell the manager about\nour Scheme).\n\n- We run our manager, which in turn runs all of our controllers and webhooks.\nThe manager is set up to run until it receives a graceful shutdown signal.\nThis way, when we're running on Kubernetes, we behave nicely with graceful\npod termination.\n\nWhile we don't have anything to run just yet, remember where that\n`+kubebuilder:scaffold:builder` comment is -- things'll get interesting there\nsoon.\n\n*/\n\nfunc main() {\n\tvar metricsAddr string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \":8080\", \"The address the metric endpoint binds to.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme: scheme,\n\t\tMetrics: server.Options{\n\t\t\tBindAddress: metricsAddr,\n\t\t},\n\t\tWebhookServer:          webhook.NewServer(webhook.Options{Port: 9443}),\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"80807133.tutorial.kubebuilder.io\",\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"unable to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\t/*\n\t\tNote that the `Manager` can restrict the namespace that all controllers will watch for resources by:\n\t*/\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme: scheme,\n\t\tCache: cache.Options{\n\t\t\tDefaultNamespaces: map[string]cache.Config{\n\t\t\t\tnamespace: {},\n\t\t\t},\n\t\t},\n\t\tMetrics: server.Options{\n\t\t\tBindAddress: metricsAddr,\n\t\t},\n\t\tWebhookServer:          webhook.NewServer(webhook.Options{Port: 9443}),\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"80807133.tutorial.kubebuilder.io\",\n\t})\n\n\t/*\n\t\tThe above example will change the scope of your project to a single `Namespace`. In this scenario,\n\t\tit is also suggested to restrict the provided authorization to this namespace by replacing the default\n\t\t`ClusterRole` and `ClusterRoleBinding` to `Role` and `RoleBinding` respectively.\n\t\tFor further information see the Kubernetes documentation about [Using RBAC Authorization](https://kubernetes.io/docs/reference/access-authn-authz/rbac/).\n\n\t\tAlso, it is possible to use the [`DefaultNamespaces`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cache#Options)\n\t\tfrom `cache.Options{}` to cache objects in a specific set of namespaces:\n\t*/\n\n\tvar namespaces []string // List of Namespaces\n\tdefaultNamespaces := make(map[string]cache.Config)\n\n\tfor _, ns := range namespaces {\n\t\tdefaultNamespaces[ns] = cache.Config{}\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme: scheme,\n\t\tCache: cache.Options{\n\t\t\tDefaultNamespaces: defaultNamespaces,\n\t\t},\n\t\tMetrics: server.Options{\n\t\t\tBindAddress: metricsAddr,\n\t\t},\n\t\tWebhookServer:          webhook.NewServer(webhook.Options{Port: 9443}),\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"80807133.tutorial.kubebuilder.io\",\n\t})\n\n\t/*\n\t\tFor further information see [`cache.Options{}`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cache#Options)\n\t*/\n\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"unable to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"unable to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"problem running manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/finalizer_example.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nFirst, we start out with some standard imports.\nAs before, we need the core controller-runtime library, as well as\nthe client package, and the package for our API types.\n*/\npackage controllers\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/kubernetes/pkg/apis/batch\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nBy default, kubebuilder will include the RBAC rules necessary to update finalizers for CronJobs.\n*/\n\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update\n\n/*\nThe code snippet below shows skeleton code for implementing a finalizer.\n*/\n\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := r.Log.WithValues(\"cronjob\", req.NamespacedName)\n\n\tcronJob := &batchv1.CronJob{}\n\tif err := r.Get(ctx, req.NamespacedName, cronJob); err != nil {\n\t\tlog.Error(err, \"unable to fetch CronJob\")\n\t\t// we'll ignore not-found errors, since they can't be fixed by an immediate\n\t\t// requeue (we'll need to wait for a new notification), and we can get them\n\t\t// on deleted requests.\n\t\treturn ctrl.Result{}, client.IgnoreNotFound(err)\n\t}\n\n\t// name of our custom finalizer\n\tmyFinalizerName := \"batch.tutorial.kubebuilder.io/finalizer\"\n\n\t// examine DeletionTimestamp to determine if object is under deletion\n\tif cronJob.ObjectMeta.DeletionTimestamp.IsZero() {\n\t\t// The object is not being deleted, so if it does not have our finalizer,\n\t\t// then let's add the finalizer and update the object. This is equivalent\n\t\t// to registering our finalizer.\n\t\tif !controllerutil.ContainsFinalizer(cronJob, myFinalizerName) {\n\t\t\tcontrollerutil.AddFinalizer(cronJob, myFinalizerName)\n\t\t\tif err := r.Update(ctx, cronJob); err != nil {\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// The object is being deleted\n\t\tif controllerutil.ContainsFinalizer(cronJob, myFinalizerName) {\n\t\t\t// our finalizer is present, so let's handle any external dependency\n\t\t\tif err := r.deleteExternalResources(cronJob); err != nil {\n\t\t\t\t// if fail to delete the external dependency here, return with error\n\t\t\t\t// so that it can be retried.\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// remove our finalizer from the list and update it.\n\t\t\tcontrollerutil.RemoveFinalizer(cronJob, myFinalizerName)\n\t\t\tif err := r.Update(ctx, cronJob); err != nil {\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\n\t\t// Stop reconciliation as the item is being deleted\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Your reconcile logic\n\n\treturn ctrl.Result{}, nil\n}\n\nfunc (r *Reconciler) deleteExternalResources(cronJob *batch.CronJob) error {\n\t//\n\t// delete any external resources associated with the cronJob\n\t//\n\t// Ensure that delete implementation is idempotent and safe to invoke\n\t// multiple times for same object.\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.github/workflows/test-chart.yml",
    "content": "name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare project\n        run: |\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n\n      - name: Install cert-manager via Helm (wait for readiness)\n        run: |\n          helm repo add jetstack https://charts.jetstack.io\n          helm repo update\n          helm install cert-manager jetstack/cert-manager \\\n            --namespace cert-manager \\\n            --create-namespace \\\n            --set crds.enabled=true \\\n            --wait \\\n            --timeout 300s\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Deploy manager via Helm\n        run: |\n          make helm-deploy IMG=project:v0.1.0\n\n      - name: Check Helm release status\n        run: |\n          make helm-status\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/AGENTS.md",
    "content": "# project - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t# Note that the option maxDescLen=0 was added in the default scaffold in order to sort out the issue\n\t# Too long: must have at most 262144 bytes. By using kubectl apply to create / update resources an annotation\n\t# is created by K8s API to store the latest version of the resource ( kubectl.kubernetes.io/last-applied-configuration).\n\t# However, it has a size limit and if the CRD is too big with so many long descriptions as this one it will cause the failure.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd:maxDescLen=0 webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-builder\n\t$(CONTAINER_TOOL) buildx use project-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= project-system\n## Name of the Helm release\nHELM_RELEASE ?= project\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= dist/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: tutorial.kubebuilder.io\nlayout:\n- go.kubebuilder.io/v4\nplugins:\n  helm.kubebuilder.io/v2-alpha:\n    manifests: dist/install.yaml\n    output: dist\nprojectName: project\nrepo: tutorial.kubebuilder.io/project\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: tutorial.kubebuilder.io\n  group: batch\n  kind: CronJob\n  path: tutorial.kubebuilder.io/project/api/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\nversion: \"3\"\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/README.md",
    "content": "# project\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/api/v1/cronjob_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\n */\n\npackage v1\n\n/*\n */\n\nimport (\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\n First, let's take a look at our spec.  As we discussed before, spec holds\n *desired state*, so any \"inputs\" to our controller go here.\n\n Fundamentally a CronJob needs the following pieces:\n\n - A schedule (the *cron* in CronJob)\n - A template for the Job to run (the\n *job* in CronJob)\n\n We'll also want a few extras, which will make our users' lives easier:\n\n - A deadline for starting jobs (if we miss this deadline, we'll just wait till\n   the next scheduled time)\n - What to do if multiple jobs would run at once (do we wait? stop the old one? run both?)\n - A way to pause the running of a CronJob, in case something's wrong with it\n - Limits on old job history\n\n Remember, since we never read our own status, we need to have some other way to\n keep track of whether a job has run.  We can use at least one old job to do\n this.\n\n We'll use several markers (`// +comment`) to specify additional metadata.  These\n will be used by [controller-tools](https://github.com/kubernetes-sigs/controller-tools) when generating our CRD manifest.\n As we'll see in a bit, controller-tools will also use GoDoc to form descriptions for\n the fields.\n*/\n\n// CronJobSpec defines the desired state of CronJob\ntype CronJobSpec struct {\n\t// schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.\n\t// +kubebuilder:validation:MinLength=0\n\t// +required\n\tSchedule string `json:\"schedule\"`\n\n\t// startingDeadlineSeconds defines in seconds for starting the job if it misses scheduled\n\t// time for any reason.  Missed jobs executions will be counted as failed ones.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tStartingDeadlineSeconds *int64 `json:\"startingDeadlineSeconds,omitempty\"`\n\n\t// concurrencyPolicy specifies how to treat concurrent executions of a Job.\n\t// Valid values are:\n\t// - \"Allow\" (default): allows CronJobs to run concurrently;\n\t// - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet;\n\t// - \"Replace\": cancels currently running job and replaces it with a new one\n\t// +optional\n\t// +kubebuilder:default:=Allow\n\tConcurrencyPolicy ConcurrencyPolicy `json:\"concurrencyPolicy,omitempty\"`\n\n\t// suspend tells the controller to suspend subsequent executions, it does\n\t// not apply to already started executions.  Defaults to false.\n\t// +optional\n\tSuspend *bool `json:\"suspend,omitempty\"`\n\n\t// jobTemplate defines the job that will be created when executing a CronJob.\n\t// +required\n\tJobTemplate batchv1.JobTemplateSpec `json:\"jobTemplate\"`\n\n\t// successfulJobsHistoryLimit defines the number of successful finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tSuccessfulJobsHistoryLimit *int32 `json:\"successfulJobsHistoryLimit,omitempty\"`\n\n\t// failedJobsHistoryLimit defines the number of failed finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tFailedJobsHistoryLimit *int32 `json:\"failedJobsHistoryLimit,omitempty\"`\n}\n\n/*\n We define a custom type to hold our concurrency policy.  It's actually\n just a string under the hood, but the type gives extra documentation,\n and allows us to attach validation on the type instead of the field,\n making the validation more easily reusable.\n*/\n\n// ConcurrencyPolicy describes how the job will be handled.\n// Only one of the following concurrent policies may be specified.\n// If none of the following policies is specified, the default one\n// is AllowConcurrent.\n// +kubebuilder:validation:Enum=Allow;Forbid;Replace\ntype ConcurrencyPolicy string\n\nconst (\n\t// AllowConcurrent allows CronJobs to run concurrently.\n\tAllowConcurrent ConcurrencyPolicy = \"Allow\"\n\n\t// ForbidConcurrent forbids concurrent runs, skipping next run if previous\n\t// hasn't finished yet.\n\tForbidConcurrent ConcurrencyPolicy = \"Forbid\"\n\n\t// ReplaceConcurrent cancels currently running job and replaces it with a new one.\n\tReplaceConcurrent ConcurrencyPolicy = \"Replace\"\n)\n\n/*\n Next, let's design our status, which holds observed state.  It contains any information\n we want users or other controllers to be able to easily obtain.\n\n We'll keep a list of actively running jobs, as well as the last time that we successfully\n ran our job.  Notice that we use `metav1.Time` instead of `time.Time` to get the stable\n serialization, as mentioned above.\n*/\n\n// CronJobStatus defines the observed state of CronJob.\ntype CronJobStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// active defines a list of pointers to currently running jobs.\n\t// +optional\n\t// +listType=atomic\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:MaxItems=10\n\tActive []corev1.ObjectReference `json:\"active,omitempty\"`\n\n\t// lastScheduleTime defines when was the last time the job was successfully scheduled.\n\t// +optional\n\tLastScheduleTime *metav1.Time `json:\"lastScheduleTime,omitempty\"`\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the CronJob resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n/*\n Finally, we have the rest of the boilerplate that we've already discussed.\n As previously noted, we don't need to change this, except to mark that\n we want a status subresource, so that we behave like built-in kubernetes types.\n*/\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// CronJob is the Schema for the cronjobs API\ntype CronJob struct {\n\t/*\n\t */\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of CronJob\n\t// +required\n\tSpec CronJobSpec `json:\"spec\"`\n\n\t// status defines the observed state of CronJob\n\t// +optional\n\tStatus CronJobStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob\ntype CronJobList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []CronJob `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&CronJob{}, &CronJobList{})\n}\n\n// +kubebuilder:docs-gen:collapse=Root Object Definitions\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/api/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nFirst, we have some *package-level* markers that denote that there are\nKubernetes objects in this package, and that this package represents the group\n`batch.tutorial.kubebuilder.io`. The `object` generator makes use of the\nformer, while the latter is used by the CRD generator to generate the right\nmetadata for the CRDs it creates from this package.\n*/\n\n// Package v1 contains API Schema definitions for the batch v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=batch.tutorial.kubebuilder.io\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\n/*\nThen, we have the commonly useful variables that help us set up our Scheme.\nSince we need to use all the types in this package in our controller, it's\nhelpful (and the convention) to have a convenient method to add all the types to\nsome other `Scheme`. SchemeBuilder makes this easy for us.\n*/\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"batch.tutorial.kubebuilder.io\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJob) DeepCopyInto(out *CronJob) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJob.\nfunc (in *CronJob) DeepCopy() *CronJob {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJob)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJob) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobList) DeepCopyInto(out *CronJobList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]CronJob, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobList.\nfunc (in *CronJobList) DeepCopy() *CronJobList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJobList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) {\n\t*out = *in\n\tif in.StartingDeadlineSeconds != nil {\n\t\tin, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds\n\t\t*out = new(int64)\n\t\t**out = **in\n\t}\n\tif in.Suspend != nil {\n\t\tin, out := &in.Suspend, &out.Suspend\n\t\t*out = new(bool)\n\t\t**out = **in\n\t}\n\tin.JobTemplate.DeepCopyInto(&out.JobTemplate)\n\tif in.SuccessfulJobsHistoryLimit != nil {\n\t\tin, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n\tif in.FailedJobsHistoryLimit != nil {\n\t\tin, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobSpec.\nfunc (in *CronJobSpec) DeepCopy() *CronJobSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobStatus) DeepCopyInto(out *CronJobStatus) {\n\t*out = *in\n\tif in.Active != nil {\n\t\tin, out := &in.Active, &out.Active\n\t\t*out = make([]corev1.ObjectReference, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.LastScheduleTime != nil {\n\t\tin, out := &in.LastScheduleTime, &out.LastScheduleTime\n\t\t*out = (*in).DeepCopy()\n\t}\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobStatus.\nfunc (in *CronJobStatus) DeepCopy() *CronJobStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t\"tutorial.kubebuilder.io/project/internal/controller\"\n\twebhookv1 \"tutorial.kubebuilder.io/project/internal/webhook/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nThe first difference to notice is that kubebuilder has added the new API\ngroup's package (`batchv1`) to our scheme.  This means that we can use those\nobjects in our controller.\n\nIf we would be using any other CRD we would have to add their scheme the same way.\nBuiltin types such as Job have their scheme added by `clientgoscheme`.\n*/\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(batchv1.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n/*\nThe other thing that's changed is that kubebuilder has added a block calling our\nCronJob controller's `SetupWithManager` method.\n*/\n\n// nolint:gocyclo\nfunc main() {\n\t/*\n\t */\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"80807133.tutorial.kubebuilder.io\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\t// +kubebuilder:docs-gen:collapse=Remaining code from main.go\n\n\tif err := (&controller.CronJobReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"CronJob\")\n\t\tos.Exit(1)\n\t}\n\n\t/*\n\t\tWe'll also set up webhooks for our type, which we'll talk about next.\n\t\tWe just need to add them to the manager.  Since we might want to run\n\t\tthe webhooks separately, or not run them when testing our controller\n\t\tlocally, we'll put them behind an environment variable.\n\n\t\tWe'll just make sure to set `ENABLE_WEBHOOKS=false` when we run locally.\n\t*/\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupCronJobWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"CronJob\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/certificate-metrics.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/certificate-webhook.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/issuer.yaml",
    "content": "# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/kustomization.yaml",
    "content": "resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/crd/bases/batch.tutorial.kubebuilder.io_cronjobs.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/batch.tutorial.kubebuilder.io_cronjobs.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\n#configurations:\n#- kustomizeconfig.yaml\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# ANCHOR: webhook-resources\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# ANCHOR_END: webhook-resources\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n- path: cert_metrics_manager_patch.yaml\n  target:\n    kind: Deployment\n\n# ANCHOR: webhook-patch\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n# ANCHOR_END: webhook-patch\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\nreplacements:\n - source: # Uncomment the following block to enable certificates for metrics\n     kind: Service\n     version: v1\n     name: controller-manager-metrics-service\n     fieldPath: metadata.name\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: metrics-certs\n       fieldPaths:\n         - spec.dnsNames.0\n         - spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n         kind: ServiceMonitor\n         group: monitoring.coreos.com\n         version: v1\n         name: controller-manager-metrics-monitor\n       fieldPaths:\n         - spec.endpoints.0.tlsConfig.serverName\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n\n - source:\n     kind: Service\n     version: v1\n     name: controller-manager-metrics-service\n     fieldPath: metadata.namespace\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: metrics-certs\n       fieldPaths:\n         - spec.dnsNames.0\n         - spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n         kind: ServiceMonitor\n         group: monitoring.coreos.com\n         version: v1\n         name: controller-manager-metrics-monitor\n       fieldPaths:\n         - spec.endpoints.0.tlsConfig.serverName\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n # ANCHOR: webhook-replacements\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n - source:\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.namespace # Namespace of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert # This name should match the one in certificate.yaml\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# ANCHOR_END: webhook-replacements\n\n# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_webhook_patch.yaml",
    "content": "# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/allow-webhook-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-webhook-traffic.yaml\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\npatches:\n  - path: monitor_tls_patch.yaml\n    target:\n      kind: ServiceMonitor\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/cronjob_admin_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over batch.tutorial.kubebuilder.io.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-admin-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/cronjob_editor_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the batch.tutorial.kubebuilder.io.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-editor-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/cronjob_viewer_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to batch.tutorial.kubebuilder.io resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-viewer-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- cronjob_admin_role.yaml\n- cronjob_editor_role.yaml\n- cronjob_viewer_role.yaml\n\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/samples/batch_v1_cronjob.yaml",
    "content": "apiVersion: batch.tutorial.kubebuilder.io/v1\nkind: CronJob\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-sample\nspec:\n  schedule: \"*/1 * * * *\"\n  startingDeadlineSeconds: 60\n  concurrencyPolicy: Allow # explicitly specify, but Allow is also default.\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          securityContext:\n            runAsNonRoot: true\n            runAsUser: 1000\n            seccompProfile:\n              type: RuntimeDefault\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            securityContext:\n              allowPrivilegeEscalation: false\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: false\n          restartPolicy: OnFailure\n  \n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- batch_v1_cronjob.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/webhook/manifests.yaml",
    "content": "---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/config/webhook/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/.helmignore",
    "content": "# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/Chart.yaml",
    "content": "apiVersion: v2\nname: project\ndescription: A Helm chart to distribute project\ntype: application\n\nversion: 0.1.0\nappVersion: \"0.1.0\"\n\nkeywords:\n  - kubernetes\n  - operator\n\nannotations:\n  kubebuilder.io/generated-by: kubebuilder\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/NOTES.txt",
    "content": "Thank you for installing {{ .Chart.Name }}.\n\nYour release is named {{ .Release.Name }}.\n\nThe controller and CRDs have been installed in namespace {{ .Release.Namespace }}.\n\nTo verify the installation:\n\n  kubectl get pods -n {{ .Release.Namespace }}\n  kubectl get customresourcedefinitions\n\nTo learn more about the release, try:\n\n  $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}\n  $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"project.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"project.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nNamespace for generated references.\nAlways uses the Helm release namespace.\n*/}}\n{{- define \"project.namespaceName\" -}}\n{{- .Release.Namespace }}\n{{- end }}\n\n{{/*\nResource name with proper truncation for Kubernetes 63-character limit.\nTakes a dict with:\n  - .suffix: Resource name suffix (e.g., \"metrics\", \"webhook\")\n  - .context: Template context (root context with .Values, .Release, etc.)\nDynamically calculates safe truncation to ensure total name length <= 63 chars.\n*/}}\n{{- define \"project.resourceName\" -}}\n{{- $fullname := include \"project.fullname\" .context }}\n{{- $suffix := .suffix }}\n{{- $maxLen := sub 62 (len $suffix) | int }}\n{{- if gt (len $fullname) $maxLen }}\n{{- printf \"%s-%s\" (trunc $maxLen $fullname | trimSuffix \"-\") $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" $fullname $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/cert-manager/metrics-certs.yaml",
    "content": "{{- if and .Values.certManager.enable .Values.metrics.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-certs\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: metrics-server-cert\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/cert-manager/selfsigned-issuer.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selfSigned: {}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/cert-manager/serving-cert.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: webhook-server-cert\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/crd/cronjobs.batch.tutorial.kubebuilder.io.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/manager/manager.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.manager.replicas }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: {{ include \"project.name\" . }}\n        helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n        app.kubernetes.io/managed-by: {{ .Release.Service }}\n        control-plane: controller-manager\n    spec:\n      {{- with .Values.manager.tolerations }}\n      tolerations: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.affinity }}\n      affinity: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      containers:\n      - args:\n        {{- if .Values.metrics.enable }}\n        - --metrics-bind-address=:{{ .Values.metrics.port }}\n        {{- else }}\n        # Bind to :0 to disable the controller-runtime managed metrics server\n        - --metrics-bind-address=0\n        {{- end }}\n        - --health-probe-bind-address=:8081\n        {{- range .Values.manager.args }}\n        - {{ . }}\n        {{- end }}\n        {{- if and .Values.certManager.enable .Values.metrics.enable }}\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n        {{- end }}\n        {{- if .Values.certManager.enable }}\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        {{- end }}\n        command:\n        - /manager\n        image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"\n        imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: {{ .Values.webhook.port }}\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          {{- if .Values.manager.resources }}\n          {{- toYaml .Values.manager.resources | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        securityContext:\n          {{- if .Values.manager.securityContext }}\n          {{- toYaml .Values.manager.securityContext | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        volumeMounts:\n        {{- if and .Values.certManager.enable .Values.metrics.enable }}\n        - mountPath: /tmp/k8s-metrics-server/metrics-certs\n          name: metrics-certs\n          readOnly: true\n        {{- end }}\n        {{- if .Values.certManager.enable }}\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n        {{- end }}\n      securityContext:\n        {{- if .Values.manager.podSecurityContext }}\n        {{- toYaml .Values.manager.podSecurityContext | nindent 8 }}\n        {{- else }}\n        {}\n        {{- end }}\n      serviceAccountName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n      terminationGracePeriodSeconds: 10\n      volumes:\n      {{- if and .Values.certManager.enable .Values.metrics.enable }}\n      - name: metrics-certs\n        secret:\n          items:\n          - key: ca.crt\n            path: ca.crt\n          - key: tls.crt\n            path: tls.crt\n          - key: tls.key\n            path: tls.key\n          optional: false\n          secretName: metrics-server-cert\n      {{- end }}\n      {{- if .Values.certManager.enable }}\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n      {{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/metrics/controller-manager-metrics-service.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: https\n    port: {{ .Values.metrics.port }}\n    protocol: TCP\n    targetPort: {{ .Values.metrics.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/prometheus/controller-manager-metrics-monitor.yaml",
    "content": "{{- if .Values.prometheus.enable }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-monitor\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  endpoints:\n  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n    path: /metrics\n    port: https\n    scheme: https\n    tlsConfig:\n      ca:\n        secret:\n          key: ca.crt\n          name: metrics-server-cert\n      cert:\n        secret:\n          key: tls.crt\n          name: metrics-server-cert\n      insecureSkipVerify: false\n      keySecret:\n        key: tls.key\n        name: metrics-server-cert\n      serverName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/controller-manager.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-admin-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-editor-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-viewer-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/leader-election-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/leader-election-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-rolebinding\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/metrics-auth-role.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/metrics-auth-rolebinding.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/rbac/metrics-reader.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-reader\" \"context\" $) }}\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/webhook/mutating-webhook-configuration.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    {{- if .Values.certManager.enable }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    {{- end }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"mutating-webhook-configuration\" \"context\" $) }}\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/webhook/validating-webhook-configuration.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    {{- if .Values.certManager.enable }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    {{- end }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"validating-webhook-configuration\" \"context\" $) }}\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/templates/webhook/webhook-service.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: {{ .Values.webhook.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/chart/values.yaml",
    "content": "## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: controller\n    tag: latest\n    pullPolicy: IfNotPresent\n\n  ## Arguments\n  ##\n  args:\n    - --leader-elect\n\n  ## Environment variables\n  ##\n  env: []\n\n  ## Env overrides (--set manager.envOverrides.VAR=value)\n  ## Same name in env above: this value takes precedence.\n  ##\n  envOverrides: {}\n\n  ## Image pull secrets\n  ##\n  imagePullSecrets: []\n\n  ## Pod-level security settings\n  ##\n  podSecurityContext:\n    runAsNonRoot: true\n    seccompProfile:\n      type: RuntimeDefault\n\n  ## Container-level security settings\n  ##\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n      drop:\n      - ALL\n    readOnlyRootFilesystem: true\n\n  ## Resource limits and requests\n  ##\n  resources:\n    limits:\n      cpu: 500m\n      memory: 128Mi\n    requests:\n      cpu: 10m\n      memory: 64Mi\n\n  ## Manager pod's affinity\n  ##\n  affinity: {}\n\n  ## Manager pod's node selector\n  ##\n  nodeSelector: {}\n\n  ## Manager pod's tolerations\n  ##\n  tolerations: []\n\n## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: 8443\n\n## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: true\n\n## Webhook server configuration\n##\nwebhook:\n  enable: true\n  # Webhook server port\n  port: 9443\n\n## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-role\n  namespace: project-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-admin-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-editor-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-viewer-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-manager-role\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-rolebinding\n  namespace: project-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager-metrics-service\n  namespace: project-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-webhook-service\n  namespace: project-system\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  selector:\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager\n  namespace: project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        command:\n        - /manager\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /tmp/k8s-metrics-server/metrics-certs\n          name: metrics-certs\n          readOnly: true\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes:\n      - name: metrics-certs\n        secret:\n          items:\n          - key: ca.crt\n            path: ca.crt\n          - key: tls.crt\n            path: tls.crt\n          - key: tls.key\n            path: tls.key\n          optional: false\n          secretName: metrics-server-cert\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-metrics-certs\n  namespace: project-system\nspec:\n  dnsNames:\n  - project-controller-manager-metrics-service.project-system.svc\n  - project-controller-manager-metrics-service.project-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-selfsigned-issuer\n  secretName: metrics-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-serving-cert\n  namespace: project-system\nspec:\n  dnsNames:\n  - project-webhook-service.project-system.svc\n  - project-webhook-service.project-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-selfsigned-issuer\n  secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-selfsigned-issuer\n  namespace: project-system\nspec:\n  selfSigned: {}\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager-metrics-monitor\n  namespace: project-system\nspec:\n  endpoints:\n  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n    path: /metrics\n    port: https\n    scheme: https\n    tlsConfig:\n      ca:\n        secret:\n          key: ca.crt\n          name: metrics-server-cert\n      cert:\n        secret:\n          key: tls.crt\n          name: metrics-server-cert\n      insecureSkipVerify: false\n      keySecret:\n        key: tls.key\n        name: metrics-server-cert\n      serverName: project-controller-manager-metrics-service.project-system.svc\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project\n      control-plane: controller-manager\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-system/project-serving-cert\n  name: project-mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-system/project-serving-cert\n  name: project-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/go.mod",
    "content": "module tutorial.kubebuilder.io/project\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.27.2\n\tgithub.com/onsi/gomega v1.38.2\n\tgithub.com/robfig/cron v1.2.0\n\tk8s.io/api v0.35.0\n\tk8s.io/apimachinery v0.35.0\n\tk8s.io/client-go v0.35.0\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/spf13/cobra v1.10.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.36.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.5.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect\n\tgoogle.golang.org/grpc v1.72.2 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.0 // indirect\n\tk8s.io/apiserver v0.35.0 // indirect\n\tk8s.io/component-base v0.35.0 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=\ngithub.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=\ngithub.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=\ngithub.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=\ngithub.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=\ngithub.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=\ngithub.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=\ngithub.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=\ngithub.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=\ngithub.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=\ngithub.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=\ngithub.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=\ngithub.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=\ngithub.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=\ngithub.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0=\ngithub.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE=\ngithub.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=\ngithub.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=\ngo.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=\ngo.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=\ngo.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=\ngo.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=\ngo.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=\ngo.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=\ngo.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=\ngo.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=\ngo.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=\ngoogle.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=\nk8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=\nk8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=\nk8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=\nk8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=\nk8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=\nk8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=\nk8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=\nk8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=\nk8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=\nk8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=\nsigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=\nsigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/controller/cronjob_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWe'll start out with some imports.  You'll see below that we'll need a few more imports\nthan those scaffolded for us.  We'll talk about each one when we use it.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/robfig/cron\"\n\tkbatch \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tref \"k8s.io/client-go/tools/reference\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n/*\nNext, we'll need a Clock, which will allow us to fake timing in our tests.\n*/\n\n// CronJobReconciler reconciles a CronJob object\ntype CronJobReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n\tClock\n}\n\n/*\nWe'll mock out the clock to make it easier to jump around in time while testing,\nthe \"real\" clock just calls `time.Now`.\n*/\ntype realClock struct{}\n\nfunc (_ realClock) Now() time.Time { return time.Now() } //nolint:staticcheck\n\n// Clock knows how to get the current time.\n// It can be used to fake out timing for testing.\ntype Clock interface {\n\tNow() time.Time\n}\n\n// +kubebuilder:docs-gen:collapse=Clock Code Implementation\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableCronJob represents the status of the CronJob reconciliation\n\ttypeAvailableCronJob = \"Available\"\n\t// typeProgressingCronJob represents the status used when the CronJob is being reconciled\n\ttypeProgressingCronJob = \"Progressing\"\n\t// typeDegradedCronJob represents the status used when the CronJob has encountered an error\n\ttypeDegradedCronJob = \"Degraded\"\n)\n\n/*\nNotice that we need a few more RBAC permissions -- since we're creating and\nmanaging jobs now, we'll need permissions for those, which means adding\na couple more [markers](/reference/markers/rbac.md).\n*/\n\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update\n// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get\n\n/*\nNow, we get to the heart of the controller -- the reconciler logic.\n*/\nvar (\n\tscheduledTimeAnnotation = \"batch.tutorial.kubebuilder.io/scheduled-at\"\n)\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the CronJob object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\n// nolint:gocyclo\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t/*\n\t\t### 1: Load the CronJob by name\n\n\t\tWe'll fetch the CronJob using our client.  All client methods take a\n\t\tcontext (to allow for cancellation) as their first argument, and the object\n\t\tin question as their last.  Get is a bit special, in that it takes a\n\t\t[`NamespacedName`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client?tab=doc#ObjectKey)\n\t\tas the middle argument (most don't have a middle argument, as we'll see\n\t\tbelow).\n\n\t\tMany client methods also take variadic options at the end.\n\t*/\n\tvar cronJob batchv1.CronJob\n\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"CronJob resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get CronJob\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Initialize status conditions if not yet present\n\tif len(cronJob.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionUnknown,\n\t\t\tReason:  \"Reconciling\",\n\t\t\tMessage: \"Starting reconciliation\",\n\t\t})\n\t\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to update CronJob status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t/*\n\t\t\tAfter updating the status, we re-fetch the CronJob to ensure we are working with\n\t\t\tthe latest version of the object from the API server.\n\n\t\t\tKubernetes uses optimistic concurrency, meaning that any update (including a\n\t\t\tstatus update) may change the resource version. If we continue reconciliation\n\t\t\twith a stale copy, subsequent updates may fail with a conflict such as:\n\t\t\t\"the object has been modified; please apply your changes to the latest version and try again\".\n\n\t\t\tBy re-fetching here, we keep our reconciliation logic in sync with the actual\n\t\t\tcluster state and avoid unnecessary conflicts and requeues.\n\t\t*/\n\t\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t/*\n\t\t### 2: List all active jobs, and update the status\n\n\t\tTo fully update our status, we'll need to list all child jobs in this namespace that belong to this CronJob.\n\t\tSimilarly to Get, we can use the List method to list the child jobs.  Notice that we use variadic options to\n\t\tset the namespace and field match (which is actually an index lookup that we set up below).\n\t*/\n\tvar childJobs kbatch.JobList\n\tif err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}); err != nil {\n\t\tlog.Error(err, \"unable to list child Jobs\")\n\t\t/*\n\t\t\tBefore updating, ensure we have the latest state of the resource to avoid\n\t\t\tconflict errors (e.g. \"the object has been modified\") that would re-trigger\n\t\t\tthe reconcile loop.\n\t\t*/\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"ReconciliationError\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to list child jobs: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\n\t\t<aside class=\"note\">\n\n\t\t<h1>What is this index about?</h1>\n\n\t\t<p>The reconciler fetches all jobs owned by the cronjob for the status. As our number of cronjobs increases,\n\t\tlooking these up can become quite slow as we have to filter through all of them. For a more efficient lookup,\n\t\tthese jobs will be indexed locally on the controller's name. A jobOwnerKey field is added to the\n\t\tcached job objects. This key references the owning controller and functions as the index. Later in this\n\t\tdocument we will configure the manager to actually index this field.</p>\n\n\t\t</aside>\n\n\t\tOnce we have all the jobs we own, we'll split them into active, successful,\n\t\tand failed jobs, keeping track of the most recent run so that we can record it\n\t\tin status.  Remember, status should be able to be reconstituted from the state\n\t\tof the world, so it's generally not a good idea to read from the status of the\n\t\troot object.  Instead, you should reconstruct it every run.  That's what we'll\n\t\tdo here.\n\n\t\tWe can check if a job is \"finished\" and whether it succeeded or failed using status\n\t\tconditions.  We'll put that logic in a helper to make our code cleaner.\n\t*/\n\n\t// find the active list of jobs\n\tvar activeJobs []*kbatch.Job\n\tvar successfulJobs []*kbatch.Job\n\tvar failedJobs []*kbatch.Job\n\tvar mostRecentTime *time.Time // find the last run so we can update the status\n\n\t/*\n\t\tWe consider a job \"finished\" if it has a \"Complete\" or \"Failed\" condition marked as true.\n\t\tStatus conditions allow us to add extensible status information to our objects that other\n\t\thumans and controllers can examine to check things like completion and health.\n\t*/\n\tisJobFinished := func(job *kbatch.Job) (bool, kbatch.JobConditionType) {\n\t\tfor _, c := range job.Status.Conditions {\n\t\t\tif (c.Type == kbatch.JobComplete || c.Type == kbatch.JobFailed) && c.Status == corev1.ConditionTrue {\n\t\t\t\treturn true, c.Type\n\t\t\t}\n\t\t}\n\n\t\treturn false, \"\"\n\t}\n\t// +kubebuilder:docs-gen:collapse=isJobFinished\n\n\t/*\n\t\tWe'll use a helper to extract the scheduled time from the annotation that\n\t\twe added during job creation.\n\t*/\n\tgetScheduledTimeForJob := func(job *kbatch.Job) (*time.Time, error) {\n\t\ttimeRaw := job.Annotations[scheduledTimeAnnotation]\n\t\tif len(timeRaw) == 0 {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\ttimeParsed, err := time.Parse(time.RFC3339, timeRaw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &timeParsed, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getScheduledTimeForJob\n\n\tfor i, job := range childJobs.Items {\n\t\t_, finishedType := isJobFinished(&job)\n\t\tswitch finishedType {\n\t\tcase \"\": // ongoing\n\t\t\tactiveJobs = append(activeJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobFailed:\n\t\t\tfailedJobs = append(failedJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobComplete:\n\t\t\tsuccessfulJobs = append(successfulJobs, &childJobs.Items[i])\n\t\t}\n\n\t\t// We'll store the launch time in an annotation, so we'll reconstitute that from\n\t\t// the active jobs themselves.\n\t\tscheduledTimeForJob, err := getScheduledTimeForJob(&job)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to parse schedule time for child job\", \"job\", &job)\n\t\t\tcontinue\n\t\t}\n\t\tif scheduledTimeForJob != nil {\n\t\t\tif mostRecentTime == nil || mostRecentTime.Before(*scheduledTimeForJob) {\n\t\t\t\tmostRecentTime = scheduledTimeForJob\n\t\t\t}\n\t\t}\n\t}\n\n\tif mostRecentTime != nil {\n\t\tcronJob.Status.LastScheduleTime = &metav1.Time{Time: *mostRecentTime}\n\t} else {\n\t\tcronJob.Status.LastScheduleTime = nil\n\t}\n\tcronJob.Status.Active = nil\n\tfor _, activeJob := range activeJobs {\n\t\tjobRef, err := ref.GetReference(r.Scheme, activeJob)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to make reference to active job\", \"job\", activeJob)\n\t\t\tcontinue\n\t\t}\n\t\tcronJob.Status.Active = append(cronJob.Status.Active, *jobRef)\n\t}\n\n\t/*\n\t\tHere, we'll log how many jobs we observed at a slightly higher logging level,\n\t\tfor debugging.  Notice how instead of using a format string, we use a fixed message,\n\t\tand attach key-value pairs with the extra information.  This makes it easier to\n\t\tfilter and query log lines.\n\t*/\n\tlog.V(1).Info(\"job count\", \"active jobs\", len(activeJobs), \"successful jobs\", len(successfulJobs), \"failed jobs\", len(failedJobs))\n\n\t// Check if CronJob is suspended\n\tisSuspended := cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend\n\n\t// Update status conditions based on current state\n\tif isSuspended {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"Suspended\",\n\t\t\tMessage: \"CronJob is suspended\",\n\t\t})\n\t} else if len(failedJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t} else if len(activeJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) are currently active\", len(activeJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"CronJob is progressing with %d active job(s)\", len(activeJobs)),\n\t\t})\n\t} else {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"AllJobsCompleted\",\n\t\t\tMessage: \"All jobs have completed successfully\",\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"NoJobsActive\",\n\t\t\tMessage: \"No jobs are currently active\",\n\t\t})\n\t}\n\n\t/*\n\t\tUsing the data we've gathered, we'll update the status of our CRD.\n\t\tJust like before, we use our client.  To specifically update the status\n\t\tsubresource, we'll use the `Status` part of the client, with the `Update`\n\t\tmethod.\n\n\t\tThe status subresource ignores changes to spec, so it's less likely to conflict\n\t\twith any other updates, and can have separate permissions.\n\t*/\n\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\tlog.Error(err, \"unable to update CronJob status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\t\tOnce we've updated our status, we can move on to ensuring that the status of\n\t\tthe world matches what we want in our spec.\n\n\t\t### 3: Clean up old jobs according to the history limit\n\n\t\tFirst, we'll try to clean up old jobs, so that we don't leave too many lying\n\t\taround.\n\t*/\n\n\t// NB: deleting these are \"best effort\" -- if we fail on a particular one,\n\t// we won't requeue just to finish the deleting.\n\tif cronJob.Spec.FailedJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(failedJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range failedJobs {\n\t\t\tif int32(i) >= int32(len(failedJobs))-*cronJob.Spec.FailedJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old failed job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old failed job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(successfulJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range successfulJobs {\n\t\t\tif int32(i) >= int32(len(successfulJobs))-*cronJob.Spec.SuccessfulJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old successful job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old successful job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ### 4: Check if we're suspended\n\n\tIf this object is suspended, we don't want to run any jobs, so we'll stop now.\n\tThis is useful if something's broken with the job we're running and we want to\n\tpause runs to investigate or putz with the cluster, without deleting the object.\n\t*/\n\n\tif cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend {\n\t\tlog.V(1).Info(\"cronjob suspended, skipping\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\t### 5: Get the next scheduled run\n\n\t\tIf we're not paused, we'll need to calculate the next scheduled run, and whether\n\t\tor not we've got a run that we haven't processed yet.\n\t*/\n\n\t/*\n\t\tWe'll calculate the next scheduled time using our helpful cron library.\n\t\tWe'll start calculating appropriate times from our last run, or the creation\n\t\tof the CronJob if we can't find a last run.\n\n\t\tIf there are too many missed runs and we don't have any deadlines set, we'll\n\t\tbail so that we don't cause issues on controller restarts or wedges.\n\n\t\tOtherwise, we'll just return the missed runs (of which we'll just use the latest),\n\t\tand the next run, so that we can know when it's time to reconcile again.\n\t*/\n\tgetNextSchedule := func(cronJob *batchv1.CronJob, now time.Time) (lastMissed time.Time, next time.Time, err error) {\n\t\tsched, err := cron.ParseStandard(cronJob.Spec.Schedule)\n\t\tif err != nil {\n\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"unparseable schedule %q: %w\", cronJob.Spec.Schedule, err)\n\t\t}\n\n\t\t// for optimization purposes, cheat a bit and start from our last observed run time\n\t\t// we could reconstitute this here, but there's not much point, since we've\n\t\t// just updated it.\n\t\tvar earliestTime time.Time\n\t\tif cronJob.Status.LastScheduleTime != nil {\n\t\t\tearliestTime = cronJob.Status.LastScheduleTime.Time\n\t\t} else {\n\t\t\tearliestTime = cronJob.CreationTimestamp.Time\n\t\t}\n\t\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\t\t// controller is not going to schedule anything below this point\n\t\t\tschedulingDeadline := now.Add(-time.Second * time.Duration(*cronJob.Spec.StartingDeadlineSeconds))\n\n\t\t\tif schedulingDeadline.After(earliestTime) {\n\t\t\t\tearliestTime = schedulingDeadline\n\t\t\t}\n\t\t}\n\t\tif earliestTime.After(now) {\n\t\t\treturn time.Time{}, sched.Next(now), nil\n\t\t}\n\n\t\tstarts := 0\n\t\tfor t := sched.Next(earliestTime); !t.After(now); t = sched.Next(t) {\n\t\t\tlastMissed = t\n\t\t\t// An object might miss several starts. For example, if\n\t\t\t// controller gets wedged on Friday at 5:01pm when everyone has\n\t\t\t// gone home, and someone comes in on Tuesday AM and discovers\n\t\t\t// the problem and restarts the controller, then all the hourly\n\t\t\t// jobs, more than 80 of them for one hourly scheduledJob, should\n\t\t\t// all start running with no further intervention (if the scheduledJob\n\t\t\t// allows concurrency and late starts).\n\t\t\t//\n\t\t\t// However, if there is a bug somewhere, or incorrect clock\n\t\t\t// on controller's server or apiservers (for setting creationTimestamp)\n\t\t\t// then there could be so many missed start times (it could be off\n\t\t\t// by decades or more), that it would eat up all the CPU and memory\n\t\t\t// of this controller. In that case, we want to not try to list\n\t\t\t// all the missed start times.\n\t\t\tstarts++\n\t\t\tif starts > 100 {\n\t\t\t\t// We can't get the most recent times so just return an empty slice\n\t\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"Too many missed start times (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.\") //nolint:staticcheck\n\t\t\t}\n\t\t}\n\t\treturn lastMissed, sched.Next(now), nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getNextSchedule\n\n\t// figure out the next times that we need to create\n\t// jobs at (or anything we missed).\n\tmissedRun, nextRun, err := getNextSchedule(&cronJob, r.Now())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to figure out CronJob schedule\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the schedule error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"InvalidSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to parse schedule: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\t// we don't really care about requeuing until we get an update that\n\t\t// fixes the schedule, so don't return an error\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\tWe'll prep our eventual request to requeue until the next job, and then figure\n\t\tout if we actually need to run.\n\t*/\n\tscheduledResult := ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())} // save this so we can re-use it elsewhere\n\tlog = log.WithValues(\"now\", r.Now(), \"next run\", nextRun)\n\n\t/*\n\t\t### 6: Run a new job if it's on schedule, not past the deadline, and not blocked by our concurrency policy\n\n\t\tIf we've missed a run, and we're still within the deadline to start it, we'll need to run a job.\n\t*/\n\tif missedRun.IsZero() {\n\t\tlog.V(1).Info(\"no upcoming scheduled times, sleeping until next\")\n\t\treturn scheduledResult, nil\n\t}\n\n\t// make sure we're not too late to start the run\n\tlog = log.WithValues(\"current run\", missedRun)\n\ttooLate := false\n\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\ttooLate = missedRun.Add(time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second).Before(r.Now())\n\t}\n\tif tooLate {\n\t\tlog.V(1).Info(\"missed starting deadline for last run, sleeping till next\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect missed deadline\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"MissedSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Missed starting deadline for run at %v\", missedRun),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn scheduledResult, nil\n\t}\n\n\t/*\n\t\tIf we actually have to run a job, we'll need to either wait till existing ones finish,\n\t\treplace the existing ones, or just add new ones.  If our information is out of date due\n\t\tto cache delay, we'll get a requeue when we get up-to-date information.\n\t*/\n\t// figure out how to run this job -- concurrency policy might forbid us from running\n\t// multiple at the same time...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ForbidConcurrent && len(activeJobs) > 0 {\n\t\tlog.V(1).Info(\"concurrency policy blocks concurrent runs, skipping\", \"num active\", len(activeJobs))\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...or instruct us to replace existing ones...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ReplaceConcurrent {\n\t\tfor _, activeJob := range activeJobs {\n\t\t\t// we don't care if the job was already deleted\n\t\t\tif err := r.Delete(ctx, activeJob, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete active job\", \"job\", activeJob)\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t\tOnce we've figured out what to do with existing jobs, we'll actually create our desired job\n\t*/\n\n\t/*\n\t\tWe need to construct a job based on our CronJob's template.  We'll copy over the spec\n\t\tfrom the template and copy some basic object meta.\n\n\t\tThen, we'll set the \"scheduled time\" annotation so that we can reconstitute our\n\t\t`LastScheduleTime` field each reconcile.\n\n\t\tFinally, we'll need to set an owner reference.  This allows the Kubernetes garbage collector\n\t\tto clean up jobs when we delete the CronJob, and allows controller-runtime to figure out\n\t\twhich cronjob needs to be reconciled when a given job changes (is added, deleted, completes, etc).\n\t*/\n\tconstructJobForCronJob := func(cronJob *batchv1.CronJob, scheduledTime time.Time) (*kbatch.Job, error) {\n\t\t// We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice\n\t\tname := fmt.Sprintf(\"%s-%d\", cronJob.Name, scheduledTime.Unix())\n\n\t\tjob := &kbatch.Job{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tLabels:      make(map[string]string),\n\t\t\t\tAnnotations: make(map[string]string),\n\t\t\t\tName:        name,\n\t\t\t\tNamespace:   cronJob.Namespace,\n\t\t\t},\n\t\t\tSpec: *cronJob.Spec.JobTemplate.Spec.DeepCopy(),\n\t\t}\n\t\tmaps.Copy(job.Annotations, cronJob.Spec.JobTemplate.Annotations)\n\t\tjob.Annotations[scheduledTimeAnnotation] = scheduledTime.Format(time.RFC3339)\n\t\tmaps.Copy(job.Labels, cronJob.Spec.JobTemplate.Labels)\n\t\tif err := ctrl.SetControllerReference(cronJob, job, r.Scheme); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn job, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=constructJobForCronJob\n\n\t// actually make the job...\n\tjob, err := constructJobForCronJob(&cronJob, missedRun)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to construct job from template\")\n\t\t// don't bother requeuing until we get a change to the spec\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...and create it on the cluster\n\tif err := r.Create(ctx, job); err != nil {\n\t\tlog.Error(err, \"unable to create Job for CronJob\", \"job\", job)\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobCreationFailed\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to create job: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tlog.V(1).Info(\"created Job for CronJob run\", \"job\", job)\n\n\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\treturn ctrl.Result{}, fetchErr\n\t}\n\t// Update status condition to reflect successful job creation\n\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\tType:    typeProgressingCronJob,\n\t\tStatus:  metav1.ConditionTrue,\n\t\tReason:  \"JobCreated\",\n\t\tMessage: fmt.Sprintf(\"Created job %s\", job.Name),\n\t})\n\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t}\n\n\t/*\n\t\t### 7: Requeue when we either see a running job or it's time for the next scheduled run\n\n\t\tFinally, we'll return the result that we prepped above, that says we want to requeue\n\t\twhen our next run would need to occur.  This is taken as a maximum deadline -- if something\n\t\telse changes in between, like our job starts or finishes, we get modified, etc, we might\n\t\treconcile again sooner.\n\t*/\n\t// we'll requeue once we see the running job, and update our status\n\treturn scheduledResult, nil\n}\n\n/*\n### Setup\n\nFinally, we'll update our setup.  In order to allow our reconciler to quickly\nlook up Jobs by their owner, we'll need an index.  We declare an index key that\nwe can later use with the client as a pseudo-field name, and then describe how to\nextract the indexed value from the Job object.  The indexer will automatically take\ncare of namespaces for us, so we just have to extract the owner name if the Job has\na CronJob owner.\n\nAdditionally, we'll inform the manager that this controller owns some Jobs, so that it\nwill automatically call Reconcile on the underlying CronJob when a Job changes, is\ndeleted, etc.\n*/\nvar (\n\tjobOwnerKey = \".metadata.controller\"\n\tapiGVStr    = batchv1.GroupVersion.String()\n)\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\t// set up a real clock, since we're not in a test\n\tif r.Clock == nil {\n\t\tr.Clock = realClock{}\n\t}\n\n\tif err := mgr.GetFieldIndexer().IndexField(context.Background(), &kbatch.Job{}, jobOwnerKey, func(rawObj client.Object) []string {\n\t\t// grab the job object, extract the owner...\n\t\tjob := rawObj.(*kbatch.Job)\n\t\towner := metav1.GetControllerOf(job)\n\t\tif owner == nil {\n\t\t\treturn nil\n\t\t}\n\t\t// ...make sure it's a CronJob...\n\t\tif owner.APIVersion != apiGVStr || owner.Kind != \"CronJob\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// ...and if so, return it\n\t\treturn []string{owner.Name}\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&batchv1.CronJob{}).\n\t\tOwns(&kbatch.Job{}).\n\t\tNamed(\"cronjob\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/controller/cronjob_controller_test.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nIdeally, we should have one `<kind>_controller_test.go` for each controller scaffolded and called in the `suite_test.go`.\nSo, let's write our example test for the CronJob controller (`cronjob_controller_test.go.`)\n*/\n\n/*\nAs usual, we start with the necessary imports.\n*/\npackage controller\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tcronjobv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nThe first step to writing a simple integration test is to actually create an instance of CronJob you can run tests against.\nNote that to create a CronJob, you'll need to create a stub CronJob struct that contains your CronJob's specifications.\n\nNote that when we create a stub CronJob, the CronJob also needs stubs of its required downstream objects.\nWithout the stubbed Job template spec and the Pod template spec below, the Kubernetes API will not be able to\ncreate the CronJob.\n*/\nvar _ = Describe(\"CronJob controller\", func() {\n\tContext(\"CronJob controller test\", func() {\n\n\t\tconst CronjobName = \"test-cronjob\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &v1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      CronjobName,\n\t\t\t\tNamespace: CronjobName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      CronjobName,\n\t\t\tNamespace: CronjobName,\n\t\t}\n\t\tcronJob := &cronjobv1.CronJob{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Get(ctx, types.NamespacedName{Name: CronjobName}, &v1.Namespace{})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\terr = k8sClient.Create(ctx, namespace)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\tBy(\"creating the custom resource for the Kind CronJob\")\n\t\t\tcronJob = &cronjobv1.CronJob{}\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, cronJob)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t/*\n\t\t\t\t\tLet's mock our custom resource the same way we would apply it from\n\t\t\t\t\tthe manifest under config/samples\n\t\t\t\t*/\n\t\t\t\tcronJob = &cronjobv1.CronJob{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      CronjobName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, cronJob)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\t/*\n\t\t\tAfter each test, we clean up the resources created above.\n\t\t*/\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind CronJob\")\n\t\t\tfound := &cronjobv1.CronJob{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\t\t})\n\n\t\t/*\n\t\t\tNow we can start implementing the test that validates the controller’s reconciliation behavior.\n\t\t*/\n\n\t\tIt(\"should successfully reconcile a custom resource for CronJob\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &cronjobv1.CronJob{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tAfter creating this CronJob, let's verify that the controller properly initializes the status conditions.\n\t\t\t\tThe controller runs in the background (started in suite_test.go), so it will automatically\n\t\t\t\tdetect our CronJob and set initial conditions.\n\t\t\t*/\n\t\t\tBy(\"Checking that status conditions are initialized\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Conditions).NotTo(BeEmpty())\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tNow let's verify the CronJob has no active jobs initially.\n\t\t\t\tWe use Gomega's `Consistently()` check here to ensure the status remains stable,\n\t\t\t\tconfirming the controller isn't creating jobs prematurely.\n\t\t\t*/\n\t\t\tBy(\"Checking that the CronJob has zero active Jobs\")\n\t\t\tConsistently(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(BeEmpty())\n\t\t\t}).WithTimeout(time.Second * 10).WithPolling(time.Millisecond * 250).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tNext, we actually create a stubbed Job that will belong to our CronJob.\n\t\t\t\tWe set the Job's status Active count to 2 to simulate the Job running two pods,\n\t\t\t\twhich means the Job is actively running.\n\n\t\t\t\tWe then set the Job's owner reference to point to our test CronJob.\n\t\t\t\tThis ensures that the test Job belongs to, and is tracked by, our test CronJob.\n\t\t\t*/\n\t\t\tBy(\"Creating a new Job owned by the CronJob\")\n\t\t\ttestJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"test-job\",\n\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// Note that your CronJob’s GroupVersionKind is required to set up this owner reference.\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\ttestJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, testJob)).To(Succeed())\n\t\t\t// Note that you can not manage the status values while creating the resource.\n\t\t\t// The status field is managed separately to reflect the current state of the resource.\n\t\t\t// Therefore, it should be updated using a PATCH or PUT operation after the resource has been created.\n\t\t\t// Additionally, it is recommended to use StatusConditions to manage the status. For further information see:\n\t\t\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status\n\t\t\ttestJob.Status.Active = 2\n\t\t\tExpect(k8sClient.Status().Update(ctx, testJob)).To(Succeed())\n\n\t\t\t/*\n\t\t\t\tAdding this Job to our test CronJob should trigger our controller's reconciler logic.\n\t\t\t\tAfter that, we can verify whether our controller eventually updates our CronJob's Status field as expected!\n\t\t\t*/\n\t\t\tBy(\"Checking that the CronJob has one active Job in status\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(HaveLen(1), \"should have exactly one active job\")\n\t\t\t\tg.Expect(cronJob.Status.Active[0].Name).To(Equal(\"test-job\"), \"the active job name should match\")\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tFinally, let's verify that the controller properly set status conditions.\n\t\t\t\tStatus conditions are a key part of Kubernetes API conventions and allow users and other\n\t\t\t\tcontrollers to understand the resource state.\n\n\t\t\t\tWhen there are active jobs, the Available condition should be True with reason JobsActive.\n\t\t\t*/\n\t\t\tBy(\"Checking the latest Status Condition added to the CronJob instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"should have one Available condition\")\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"Available should be True\")\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"JobsActive\"), \"reason should be JobsActive\")\n\t\t})\n\t})\n})\n\n/*\n\tAfter writing all this code, you can run `make test` or `go test ./...` in your `controllers/` directory again to run your new test!\n*/\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/controller/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWhen we created the CronJob API with `kubebuilder create api` in a [previous chapter](/cronjob-tutorial/new-api.md), Kubebuilder already did some test work for you.\nKubebuilder scaffolded a `internal/controller/suite_test.go` file that does the bare bones of setting up a test environment.\n\nFirst, it will contain the necessary imports.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNow, let's go through the code generated.\n*/\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client // You'll be using this client in your tests.\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\t/*\n\t\tThe CronJob Kind is added to the runtime scheme used by the test environment.\n\t\tThis ensures that the CronJob API is registered with the scheme, allowing the test controller to recognize and interact with CronJob resources.\n\t*/\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\t/*\n\t\tAfter the schemas, you will see the following marker.\n\t\tThis marker is what allows new schemas to be added here automatically when a new API is added to the project.\n\t*/\n\n\t// +kubebuilder:scaffold:scheme\n\n\t/*\n\t\tThe envtest environment is configured to load Custom Resource Definitions (CRDs) from the specified directory.\n\t\tThis setup enables the test environment to recognize and interact with the custom resources defined by these CRDs.\n\t*/\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\t/*\n\t\tThen, we start the envtest cluster.\n\t*/\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\t/*\n\t\tA client is created for our test CRUD operations.\n\t*/\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t/*\n\t\tOne thing that this autogenerated file is missing, however, is a way to actually start your controller.\n\t\tThe code above will set up a client for interacting with your custom Kind,\n\t\tbut will not be able to test your controller behavior.\n\t\tIf you want to test your custom controller logic, you’ll need to add some familiar-looking manager logic\n\t\tto your BeforeSuite() function, so you can register your custom controller to run on this test cluster.\n\n\t\tYou may notice that the code below runs your controller with nearly identical logic to your CronJob project’s main.go!\n\t\tThe only difference is that the manager is started in a separate goroutine so it does not block the cleanup of envtest\n\t\twhen you’re done running your tests.\n\n\t\tNote that we set up both a \"live\" k8s client and a separate client from the manager. This is because when making\n\t\tassertions in tests, you generally want to assert against the live state of the API server. If you use the client\n\t\tfrom the manager (`k8sManager.GetClient`), you'd end up asserting against the contents of the cache instead, which is\n\t\tslower and can introduce flakiness into your tests. We could use the manager's `APIReader` to accomplish the same\n\t\tthing, but that would leave us with two clients in our test assertions and setup (one for reading, one for writing),\n\t\tand it'd be easy to make mistakes.\n\n\t\tNote that we keep the reconciler running against the manager's cache client, though -- we want our controller to\n\t\tbehave as it would in production, and we use features of the cache (like indices) in our controller which aren't\n\t\tavailable when talking directly to the API server.\n\t*/\n\tk8sManager, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t})\n\tExpect(err).ToNot(HaveOccurred())\n\n\terr = (&CronJobReconciler{\n\t\tClient: k8sManager.GetClient(),\n\t\tScheme: k8sManager.GetScheme(),\n\t}).SetupWithManager(k8sManager)\n\tExpect(err).ToNot(HaveOccurred())\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = k8sManager.Start(ctx)\n\t\tExpect(err).ToNot(HaveOccurred(), \"failed to run manager\")\n\t}()\n})\n\n/*\nKubebuilder also generates boilerplate functions for cleaning up envtest and actually running your test files in your controllers/ directory.\nYou won't need to touch these.\n*/\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n/*\nNow that you have your controller running on a test cluster and a client ready to perform operations on your CronJob, we can start writing integration tests!\n*/\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v1\n\nimport (\n\t\"context\"\n\n\t\"github.com/robfig/cron\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tvalidationutils \"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNext, we'll setup a logger for the webhooks.\n*/\n\nvar cronjoblog = logf.Log.WithName(\"cronjob-resource\")\n\n/*\nThen, we set up the webhook with the manager.\n*/\n\n// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.\nfunc SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &batchv1.CronJob{}).\n\t\tWithValidator(&CronJobCustomValidator{}).\n\t\tWithDefaulter(&CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}).\n\t\tComplete()\n}\n\n/*\nNotice that we use kubebuilder markers to generate webhook manifests.\nThis marker is responsible for generating a mutating webhook manifest.\n\nThe meaning of each marker can be found [here](/reference/markers/webhook.md).\n*/\n\n/*\nThis marker is responsible for generating a mutation webhook manifest.\n*/\n\n// +kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=mcronjob-v1.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind CronJob when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomDefaulter struct {\n\n\t// Default values for various CronJob fields\n\tDefaultConcurrencyPolicy          batchv1.ConcurrencyPolicy\n\tDefaultSuspend                    bool\n\tDefaultSuccessfulJobsHistoryLimit int32\n\tDefaultFailedJobsHistoryLimit     int32\n}\n\n/*\nWe use the `webhook.CustomDefaulter`interface to set defaults to our CRD.\nA webhook will automatically be served that calls this defaulting.\n\nThe `Default`method is expected to mutate the receiver, setting the defaults.\n*/\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.\nfunc (d *CronJobCustomDefaulter) Default(_ context.Context, obj *batchv1.CronJob) error {\n\tcronjoblog.Info(\"Defaulting for CronJob\", \"name\", obj.GetName())\n\n\t// Set default values\n\td.applyDefaults(obj)\n\treturn nil\n}\n\n// applyDefaults applies default values to CronJob fields.\nfunc (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) {\n\tif cronJob.Spec.ConcurrencyPolicy == \"\" {\n\t\tcronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy\n\t}\n\tif cronJob.Spec.Suspend == nil {\n\t\tcronJob.Spec.Suspend = new(bool)\n\t\t*cronJob.Spec.Suspend = d.DefaultSuspend\n\t}\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit == nil {\n\t\tcronJob.Spec.SuccessfulJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit\n\t}\n\tif cronJob.Spec.FailedJobsHistoryLimit == nil {\n\t\tcronJob.Spec.FailedJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit\n\t}\n}\n\n/*\nWe can validate our CRD beyond what's possible with declarative\nvalidation. Generally, declarative validation should be sufficient, but\nsometimes more advanced use cases call for complex validation.\n\nFor instance, we'll see below that we use this to validate a well-formed cron\nschedule without making up a long regular expression.\n\nIf `webhook.CustomValidator` interface is implemented, a webhook will automatically be\nserved that calls the validation.\n\nThe `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected\nto validate its receiver upon creation, update and deletion respectively.\nWe separate out ValidateCreate from ValidateUpdate to allow behavior like making\ncertain fields immutable, so that they can only be set on creation.\nValidateDelete is also separated from ValidateUpdate to allow different\nvalidation behavior on deletion.\nHere, however, we just use the same shared validation for `ValidateCreate` and\n`ValidateUpdate`. And we do nothing in `ValidateDelete`, since we don't need to\nvalidate anything on deletion.\n*/\n\n/*\nThis marker is responsible for generating a validation webhook manifest.\n*/\n\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob-v1.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomValidator struct is responsible for validating the CronJob resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateCreate(_ context.Context, obj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon creation\", \"name\", obj.GetName())\n\n\treturn nil, validateCronJob(obj)\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon update\", \"name\", newObj.GetName())\n\n\treturn nil, validateCronJob(newObj)\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateDelete(_ context.Context, obj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n\n/*\nWe validate the name and the spec of the CronJob.\n*/\n\n// validateCronJob validates the fields of a CronJob object.\nfunc validateCronJob(cronjob *batchv1.CronJob) error {\n\tvar allErrs field.ErrorList\n\tif err := validateCronJobName(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif err := validateCronJobSpec(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif len(allErrs) == 0 {\n\t\treturn nil\n\t}\n\n\treturn apierrors.NewInvalid(\n\t\tschema.GroupKind{Group: \"batch.tutorial.kubebuilder.io\", Kind: \"CronJob\"},\n\t\tcronjob.Name, allErrs)\n}\n\n/*\nSome fields are declaratively validated by OpenAPI schema.\nYou can find kubebuilder validation markers (prefixed\nwith `// +kubebuilder:validation`) in the\n[Designing an API](api-design.md) section.\nYou can find all of the kubebuilder supported markers for\ndeclaring validation by running `controller-gen crd -w`,\nor [here](/reference/markers/crd-validation.md).\n*/\n\nfunc validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error {\n\t// The field helpers from the kubernetes API machinery help us return nicely\n\t// structured validation errors.\n\treturn validateScheduleFormat(\n\t\tcronjob.Spec.Schedule,\n\t\tfield.NewPath(\"spec\").Child(\"schedule\"))\n}\n\n/*\nWe'll need to validate the [cron](https://en.wikipedia.org/wiki/Cron) schedule\nis well-formatted.\n*/\n\nfunc validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error {\n\tif _, err := cron.ParseStandard(schedule); err != nil {\n\t\treturn field.Invalid(fldPath, schedule, err.Error())\n\t}\n\treturn nil\n}\n\n/*\nValidating the length of a string field can be done declaratively by\nthe validation schema.\n\nBut the `ObjectMeta.Name` field is defined in a shared package under\nthe apimachinery repo, so we can't declaratively validate it using\nthe validation schema.\n*/\n\nfunc validateCronJobName(cronjob *batchv1.CronJob) *field.Error {\n\tif len(cronjob.Name) > validationutils.DNS1035LabelMaxLength-11 {\n\t\t// The job name length is 63 characters like all Kubernetes objects\n\t\t// (which must fit in a DNS subdomain). The cronjob controller appends\n\t\t// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating\n\t\t// a job. The job name length limit is 63 characters. Therefore cronjob\n\t\t// names must have length <= 63-11=52. If we don't validate this here,\n\t\t// then job creation will fail later.\n\t\treturn field.Invalid(field.NewPath(\"metadata\").Child(\"name\"), cronjob.Name, \"must be no more than 52 characters\")\n\t}\n\treturn nil\n}\n\n// +kubebuilder:docs-gen:collapse=validateCronJobName() Code Implementation\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n\t\"k8s.io/utils/ptr\"\n)\n\nvar _ = Describe(\"CronJob Webhook\", func() {\n\tvar (\n\t\tobj       *batchv1.CronJob\n\t\toldObj    *batchv1.CronJob\n\t\tvalidator CronJobCustomValidator\n\t\tdefaulter CronJobCustomDefaulter\n\t)\n\n\tconst validCronJobName = \"valid-cronjob-name\"\n\tconst schedule = \"*/5 * * * *\"\n\n\tBeforeEach(func() {\n\t\tobj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*obj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*obj.Spec.FailedJobsHistoryLimit = 1\n\n\t\toldObj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*oldObj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*oldObj.Spec.FailedJobsHistoryLimit = 1\n\n\t\tvalidator = CronJobCustomValidator{}\n\t\tdefaulter = CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}\n\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating CronJob under Defaulting Webhook\", func() {\n\t\tIt(\"Should apply defaults when a required field is empty\", func() {\n\t\t\tBy(\"simulating a scenario where defaults should be applied\")\n\t\t\tobj.Spec.ConcurrencyPolicy = \"\"           // This should default to AllowConcurrent\n\t\t\tobj.Spec.Suspend = nil                    // This should default to false\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = nil // This should default to 3\n\t\t\tobj.Spec.FailedJobsHistoryLimit = nil     // This should default to 1\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\n\t\t\tBy(\"checking that the default values are set\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), \"Expected ConcurrencyPolicy to default to AllowConcurrent\")\n\t\t\tExpect(*obj.Spec.Suspend).To(BeFalse(), \"Expected Suspend to default to false\")\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), \"Expected SuccessfulJobsHistoryLimit to default to 3\")\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), \"Expected FailedJobsHistoryLimit to default to 1\")\n\t\t})\n\n\t\tIt(\"Should not overwrite fields that are already set\", func() {\n\t\t\tBy(\"setting fields that would normally get a default\")\n\t\t\tobj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent\n\t\t\tobj.Spec.Suspend = ptr.To(true)\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = ptr.To(int32(5))\n\t\t\tobj.Spec.FailedJobsHistoryLimit = ptr.To(int32(2))\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\n\t\t\tBy(\"checking that the fields were not overwritten\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), \"Expected ConcurrencyPolicy to retain its set value\")\n\t\t\tExpect(obj.Spec.Suspend).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.Suspend).To(BeTrue(), \"Expected Suspend to retain its set value\")\n\t\t\tExpect(obj.Spec.SuccessfulJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), \"Expected SuccessfulJobsHistoryLimit to retain its set value\")\n\t\t\tExpect(obj.Spec.FailedJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), \"Expected FailedJobsHistoryLimit to retain its set value\")\n\t\t})\n\t})\n\n\tContext(\"When creating or updating CronJob under Validating Webhook\", func() {\n\t\tIt(\"Should deny creation if the name is too long\", func() {\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"must be no more than 52 characters\")),\n\t\t\t\t\"Expected name validation to fail for a too-long name\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the name is valid\", func() {\n\t\t\tobj.Name = validCronJobName\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected name validation to pass for a valid name\")\n\t\t})\n\n\t\tIt(\"Should deny creation if the schedule is invalid\", func() {\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"Expected exactly 5 fields, found 1: invalid-cron-schedule\")),\n\t\t\t\t\"Expected spec validation to fail for an invalid schedule\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the schedule is valid\", func() {\n\t\t\tobj.Spec.Schedule = schedule\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected spec validation to pass for a valid schedule\")\n\t\t})\n\n\t\tIt(\"Should deny update if both name and spec are invalid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).Error().To(HaveOccurred(),\n\t\t\t\t\"Expected validation to fail for both name and spec\")\n\t\t})\n\n\t\tIt(\"Should admit update if both name and spec are valid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"valid-cronjob-name-updated\"\n\t\t\tobj.Spec.Schedule = \"0 0 * * *\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil(),\n\t\t\t\t\"Expected validation to pass for a valid update\")\n\t\t})\n\t})\n\n})\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/internal/webhook/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCronJobWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"tutorial.kubebuilder.io/project/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n\t// shouldCleanupPrometheus tracks whether Prometheus was installed by this suite.\n\tshouldCleanupPrometheus = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"Ensure that Prometheus is enabled\")\n\t_ = utils.UncommentCode(\"config/default/kustomization.yaml\", \"#- ../prometheus\", \"#\")\n\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n\tBy(\"checking if Prometheus is already installed\")\n\tif !utils.IsPrometheusCRDsInstalled() {\n\t\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\t\tshouldCleanupPrometheus = true\n\n\t\tBy(\"installing Prometheus Operator\")\n\t\tExpect(utils.InstallPrometheusOperator()).To(Succeed(), \"Failed to install Prometheus Operator\")\n\t}\n\n})\n\nvar _ = AfterSuite(func() {\n\t// Teardown Prometheus if it was installed by this suite\n\tif shouldCleanupPrometheus {\n\t\tBy(\"uninstalling Prometheus Operator\")\n\t\tutils.UninstallPrometheusOperator()\n\t}\n\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"tutorial.kubebuilder.io/project/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"validating that the ServiceMonitor for Prometheus is applied in the namespace\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"ServiceMonitor\", \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"ServiceMonitor should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\t\t\"-l\", \"kubernetes.io/service-name=project-webhook-service\",\n\t\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t\t}\n\t\t\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the mutating webhook server is ready\")\n\t\t\tverifyMutatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"MutatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Mutating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyMutatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the validating webhook server is ready\")\n\t\t\tverifyValidatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"ValidatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Validating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyValidatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should provisioned cert-manager\", func() {\n\t\t\tBy(\"validating that cert-manager has the certificate Secret\")\n\t\t\tverifyCertManager := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t\t\t_, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t\tEventually(verifyCertManager).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for mutating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for mutating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tmwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for validating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for validating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/testdata/project/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n\n\tprometheusOperatorVersion = \"v0.89.0\"\n\tprometheusOperatorURL     = \"https://github.com/prometheus-operator/prometheus-operator/\" +\n\t\t\"releases/download/%s/bundle.yaml\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.\nfunc InstallPrometheusOperator() error {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"create\", \"-f\", url)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// UninstallPrometheusOperator uninstalls the prometheus\nfunc UninstallPrometheusOperator() {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed\n// by verifying the existence of key CRDs related to Prometheus.\nfunc IsPrometheusCRDsInstalled() bool {\n\t// List of common Prometheus CRDs\n\tprometheusCRDs := []string{\n\t\t\"prometheuses.monitoring.coreos.com\",\n\t\t\"prometheusrules.monitoring.coreos.com\",\n\t\t\"prometheusagents.monitoring.coreos.com\",\n\t}\n\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"custom-columns=NAME:.metadata.name\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range prometheusCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/webhook-implementation.md",
    "content": "# Implementing defaulting/validating webhooks\n\nIf you want to implement [admission webhooks](../reference/admission-webhook.md)\nfor your CRD, the only thing you need to do is to implement the `CustomDefaulter`\nand (or) the `CustomValidator` interface.\n\nKubebuilder takes care of the rest for you, such as\n\n1. Creating the webhook server.\n1. Ensuring the server has been added in the manager.\n1. Creating handlers for your webhooks.\n1. Registering each handler with a path in your server.\n\nFirst, let's scaffold the webhooks for our CRD (CronJob). We'll need to run the following command with the `--defaulting` and `--programmatic-validation` flags (since our test project will use defaulting and validating webhooks):\n\n```bash\nkubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation\n```\n\nThis will scaffold the webhook functions and register your webhook with the manager in your `main.go` for you.\n\n## Custom Webhook Paths\n\nYou can specify custom HTTP paths for your webhooks using the `--defaulting-path` and `--validation-path` flags:\n\n```bash\n# Custom path for defaulting webhook\nkubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --defaulting-path=/my-custom-mutate-path\n\n# Custom path for validation webhook\nkubebuilder create webhook --group batch --version v1 --kind CronJob --programmatic-validation --validation-path=/my-custom-validate-path\n\n# Both webhooks with different custom paths\nkubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation \\\n  --defaulting-path=/custom-mutate --validation-path=/custom-validate\n```\n\nThis changes the path in the webhook marker annotation but does not change where the webhook files are scaffolded. The webhook files will still be created in `internal/webhook/v1/`.\n\n<aside class=\"note\">\n<h1>Version Requirements</h1>\n\nCustom webhook paths require **controller-runtime v0.21+**. In earlier versions (< `v0.21`), the webhook path must follow a specific pattern and cannot be customized. The path is automatically generated based on the resource's group, version, and kind (e.g., `/mutate-batch-v1-cronjob`).\n\n</aside>\n\n{{#literatego ./testdata/project/internal/webhook/v1/cronjob_webhook.go}}\n"
  },
  {
    "path": "docs/book/src/cronjob-tutorial/writing-tests.md",
    "content": "# Writing controller tests\n\nTesting Kubernetes controllers is a big subject, and the boilerplate testing\nfiles generated for you by kubebuilder are fairly minimal.\n\nTo walk you through integration testing patterns for Kubebuilder-generated controllers, we will revisit the CronJob we built in our first tutorial and write a simple test for it.\n\nThe basic approach is that, in your generated `suite_test.go` file, you will use envtest to create a local Kubernetes API server, instantiate and run your controllers, and then write additional `*_test.go` files to test it using [Ginkgo](http://onsi.github.io/ginkgo).\n\nIf you want to tinker with how your envtest cluster is configured, see section [Configuring envtest for integration tests](../reference/envtest.md) as well as the [`envtest docs`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest?tab=doc).\n\n<aside class=\"note\">\n<h1>Other Examples</h1>\n\nIf you would like to see additional examples of controller tests generated by Kubebuilder, take a look at the <a href=\"../plugins/available/deploy-image-plugin-v1-alpha.md\">Deploy Image plugin (deploy-image/v1-alpha)</a>.\nThis plugin scaffolds APIs and controllers for managing an Operand (container image) on a cluster, following recommended patterns.\n\nThe plugin abstracts much of the complexity of managing Deployments while still allowing you to customize the generated code.\nWhen enabled, it also generates a controller test that uses <a href=\"../reference/envtest.md\">ENV TEST</a> to verify that the expected Deployment is created.\n\nAn example of this test can be found under the `testdata` directory in the Kubebuilder repository:\n<a href=\"https://github.com/kubernetes-sigs/kubebuilder/blob/master/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go\">testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go</a>.\n</aside>\n\n## Test Environment Setup\n\n{{#literatego ../cronjob-tutorial/testdata/project/internal/controller/suite_test.go}}\n\n## Testing your Controller's Behavior\n\n{{#literatego ../cronjob-tutorial/testdata/project/internal/controller/cronjob_controller_test.go}}\n\nThis Status update example above demonstrates a general testing strategy for a custom Kind with downstream objects. By this point, you hopefully have learned the following methods for testing your controller behavior:\n\n* Setting up your controller to run on an envtest cluster\n* Writing stubs for creating test objects\n* Isolating changes to an object to test specific controller behavior\n"
  },
  {
    "path": "docs/book/src/faq.md",
    "content": "\n# FAQ\n\n<aside class=\"note\">\n<h1> Controller-Runtime FAQ </h1>\n\nKubebuilder is developed on top of the [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime)\nand [controller-tools](https://github.com/kubernetes-sigs/controller-tools) libraries. We recommend you also check\nthe [Controller-Runtime FAQ page](https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md).\n</aside>\n\n\n## How does the value informed via the domain flag (i.e. `kubebuilder init --domain example.com`) when we init a project?\n\nAfter creating a project, usually you will want to extend the Kubernetes APIs and define new APIs which will be owned by your project. Therefore, the domain value is tracked in the [PROJECT][project-file-def] file which defines the config of your project and will be used as a domain to create the endpoints of your API(s). Please, ensure that you understand the [Groups and Versions and Kinds, oh my!][gvk].\n\nThe domain is for the group suffix, to explicitly show the resource group category.\nFor example, if set `--domain=example.com`:\n```\nkubebuilder init --domain example.com --repo xxx --plugins=go/v4\nkubebuilder create api --group mygroup --version v1beta1 --kind Mykind\n```\nThen the result resource group will be `mygroup.example.com`.\n\n> If domain field not set, the default value is `my.domain`.\n\n## I'd like to customize my project to use [klog][klog] instead of the [zap][zap] provided by controller-runtime. How to use `klog` or other loggers as the project logger?\n\nIn the `main.go` you can replace:\n```go\n    opts := zap.Options{\n    Development: true,\n    }\n    opts.BindFlags(flag.CommandLine)\n    flag.Parse()\n\n    ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n```\nwith:\n```go\n    flag.Parse()\n\tctrl.SetLogger(klog.NewKlogr())\n```\n\n## After `make run`, I see errors like \"unable to find leader election namespace: not running in-cluster...\"\n\nYou can enable the leader election. However, if you are testing the project locally using the `make run`\ntarget which will run the manager outside of the cluster then, you might also need to set the\nnamespace the leader election resource will be created, as follows:\n```go\nmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                  scheme,\n\t\tMetricsBindAddress:      metricsAddr,\n\t\tPort:                    9443,\n\t\tHealthProbeBindAddress:  probeAddr,\n\t\tLeaderElection:          enableLeaderElection,\n\t\tLeaderElectionID:        \"14be1926.testproject.org\",\n\t\tLeaderElectionNamespace: \"<project-name>-system\",\n```\n\nIf you are running the project on the cluster with `make deploy` target\nthen, you might not want to add this option. So, you might want to customize this behaviour using\nenvironment variables to only add this option for development purposes, such as:\n\n```go\n    leaderElectionNS := \"\"\n\tif os.Getenv(\"ENABLE_LEADER_ELECTION_NAMESPACE\") != \"false\" {\n\t\tleaderElectionNS = \"<project-name>-system\"\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                  scheme,\n\t\tMetricsBindAddress:      metricsAddr,\n\t\tPort:                    9443,\n\t\tHealthProbeBindAddress:  probeAddr,\n\t\tLeaderElection:          enableLeaderElection,\n\t\tLeaderElectionNamespace: leaderElectionNS,\n\t\tLeaderElectionID:        \"14be1926.testproject.org\",\n\t\t...\n```\n\n## I am facing the error \"open /var/run/secrets/kubernetes.io/serviceaccount/token: permission denied\" when I deploy my project against Kubernetes old versions. How to sort it out?\n\nIf you are facing the error:\n```\n1.6656687258729894e+09  ERROR   controller-runtime.client.config        unable to get kubeconfig        {\"error\": \"open /var/run/secrets/kubernetes.io/serviceaccount/token: permission denied\"}\nsigs.k8s.io/controller-runtime/pkg/client/config.GetConfigOrDie\n        /go/pkg/mod/sigs.k8s.io/controller-runtime@v0.13.0/pkg/client/config/config.go:153\nmain.main\n        /workspace/main.go:68\nruntime.main\n        /usr/local/go/src/runtime/proc.go:250\n```\nwhen you are running the project against a Kubernetes old version (maybe <= 1.21) , it might be caused by the [issue][permission-issue] , the reason is the mounted token file set to `0600`, see [solution][permission-PR] here. Then, the workaround is:\n\nAdd `fsGroup` in the manager.yaml\n```yaml\nsecurityContext:\n        runAsNonRoot: true\n        fsGroup: 65532 # add this fsGroup to make the token file readable\n```\nHowever, note that this problem is fixed and will not occur if you deploy the project in high versions (maybe >= 1.22).\n\n## The error `Too long: must have at most 262144 bytes` is faced when I run `make install` to apply the CRD manifests. How to solve it? Why this error is faced?\n\nWhen attempting to run `make install` to apply the CRD manifests, the error `Too long: must have at most 262144 bytes may be encountered.` This error arises due to a size limit enforced by the Kubernetes API. Note that the `make install` target will apply the CRD manifest under `config/crd` using `kubectl apply -f -`. Therefore, when the apply command is used, the API annotates the object with the `last-applied-configuration` which contains the entire previous configuration. If this configuration is too large, it will exceed the allowed byte size. ([More info][k8s-obj-creation])\n\nIn ideal approach might use client-side apply might seem like the perfect solution since with the entire object configuration doesn't have to be stored as an annotation (last-applied-configuration) on the server. However, it's worth noting that as of now, it isn't supported by controller-gen or kubebuilder. For more on this, refer to: [Controller-tool-discussion][controller-tool-pr].\n\nTherefore, you have a few options to workround this scenario such as:\n\n**By removing the descriptions from CRDs:**\n\nYour CRDs are generated using [controller-gen][controller-gen]. By using the option `maxDescLen=0` to remove the description, you may reduce the size, potentially resolving the issue. To do it you can update the Makefile as the following example and then, call the target `make manifest` to regenerate your CRDs without description, see:\n\n```shell\n\n .PHONY: manifests\n manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n     # Note that the option maxDescLen=0 was added in the default scaffold in order to sort out the issue\n     # Too long: must have at most 262144 bytes. By using kubectl apply to create / update resources an annotation\n     # is created by K8s API to store the latest version of the resource ( kubectl.kubernetes.io/last-applied-configuration).\n     # However, it has a size limit and if the CRD is too big with so many long descriptions as this one it will cause the failure.\n \t$(CONTROLLER_GEN) rbac:roleName=manager-role crd:maxDescLen=0 webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n```\n**By re-design your APIs:**\n\nYou can review the design of your APIs and see if it has not more specs than should be by hurting single responsibility principle for example. So that you might to re-design them.\n\n## How can I validate and parse fields in CRDs effectively?\n\nTo enhance user experience, it is recommended to use [OpenAPI v3 schema](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schemaObject) validation when writing your CRDs. However, this approach can sometimes require an additional parsing step.\nFor example, consider this code\n```go\ntype StructName struct {\n\t// +kubebuilder:validation:Format=date-time\n\tTimeField string `json:\"timeField,omitempty\"`\n}\n```\n\n### What happens in this scenario?\n\n- Users will receive an error notification from the Kubernetes API if they attempt to create a CRD with an invalid timeField value.\n- On the developer side, the string value needs to be parsed manually before use.\n\n### Is there a better approach?\n\nTo provide both a better user experience and a streamlined developer experience, it is advisable to use predefined types like [`metav1.Time`](https://pkg.go.dev/k8s.io/apimachinery@v0.31.1/pkg/apis/meta/v1#Time)\nFor example, consider this code\n```go\ntype StructName struct {\n\tTimeField metav1.Time `json:\"timeField,omitempty\"`\n}\n```\n\n### What happens in this scenario?\n\n- Users still receive error notifications from the Kubernetes API for invalid `timeField` values.\n- Developers can directly use the parsed TimeField in their code without additional parsing, reducing errors and improving efficiency.\n\n\n\n[k8s-obj-creation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/#how-to-create-objects\n[gvk]: ./cronjob-tutorial/gvks.md\n[project-file-def]: ./reference/project-config.md\n[klog]: https://github.com/kubernetes/klog\n[zap]: https://github.com/uber-go/zap\n[permission-issue]: https://github.com/kubernetes/kubernetes/issues/82573\n[permission-PR]: https://github.com/kubernetes/kubernetes/pull/89193\n[controller-gen]: ./reference/controller-gen.html\n[controller-tool-pr]: https://github.com/kubernetes-sigs/controller-tools/pull/536"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.github/workflows/auto_update.yml",
    "content": "name: Auto Update\n\n# The 'kubebuilder alpha update' command requires write access to the repository to create a branch\n# with the update files and allow you to open a pull request using the link provided in the issue.\n# The branch created will be named in the format kubebuilder-update-from-<from-version>-to-<to-version> by default.\n# To protect your codebase, please ensure that you have branch protection rules configured for your \n# main branches. This will guarantee that no one can bypass a review and push directly to a branch like 'main'.\npermissions:\n  contents: write  # Create and push the update branch\n  issues: write    # Create GitHub Issue with PR link\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * 2\" # Every Tuesday at 00:00 UTC\n\njobs:\n  auto-update:\n    runs-on: ubuntu-latest\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    # Checkout the repository.\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        fetch-depth: 0\n\n    # Configure Git to create commits with the GitHub Actions bot.\n    - name: Configure Git\n      run: |\n        git config --global user.name \"github-actions[bot]\"\n        git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n\n    # Set up Go environment.\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: stable\n\n    # Install Kubebuilder.\n    - name: Install Kubebuilder\n      run: |\n        curl -L -o kubebuilder \"https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)\"\n        chmod +x kubebuilder\n        sudo mv kubebuilder /usr/local/bin/\n        kubebuilder version\n\n    # Run the Kubebuilder alpha update command.\n    # More info: https://kubebuilder.io/reference/commands/alpha_update\n    - name: Run kubebuilder alpha update\n      # Executes the update command with specified flags.\n      # --force: Completes the merge even if conflicts occur, leaving conflict markers.\n      # --push: Automatically pushes the resulting output branch to the 'origin' remote.\n      # --restore-path: Preserves specified paths (e.g., CI workflow files) when squashing.\n      # --open-gh-issue: Creates a GitHub Issue with a link for opening a PR for review.\n      #\n      # WARNING: This workflow does not use GitHub Models AI summary by default.\n      # To enable AI-generated summaries in GitHub issues, you need permissions to use GitHub Models.\n      # If you have the required permissions, re-run:\n      #   kubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models\n      run: |\n        kubebuilder alpha update \\\n          --force \\\n          --push \\\n          --restore-path .github/workflows \\\n          --open-gh-issue\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.github/workflows/test-chart.yml",
    "content": "name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare project\n        run: |\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n# TODO: Uncomment if cert-manager is enabled\n#      - name: Install cert-manager via Helm (wait for readiness)\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager \\\n#            --namespace cert-manager \\\n#            --create-namespace \\\n#            --set crds.enabled=true \\\n#            --wait \\\n#            --timeout 300s\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Deploy manager via Helm\n        run: |\n          make helm-deploy IMG=project:v0.1.0\n\n      - name: Check Helm release status\n        run: |\n          make helm-status\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/AGENTS.md",
    "content": "# project - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-builder\n\t$(CONTAINER_TOOL) buildx use project-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= project-system\n## Name of the Helm release\nHELM_RELEASE ?= project\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= dist/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: example.com\nlayout:\n- go.kubebuilder.io/v4\nplugins:\n  autoupdate.kubebuilder.io/v1-alpha: {}\n  helm.kubebuilder.io/v2-alpha:\n    manifests: dist/install.yaml\n    output: dist\nprojectName: project\nrepo: example.com/memcached\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: example.com\n  group: cache\n  kind: Memcached\n  path: example.com/memcached/api/v1alpha1\n  version: v1alpha1\nversion: \"3\"\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/README.md",
    "content": "# project\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/api/v1alpha1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha1 contains API Schema definitions for the cache v1alpha1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=cache.example.com\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"cache.example.com\", Version: \"v1alpha1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/api/v1alpha1/memcached_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n// MemcachedSpec defines the desired state of Memcached\ntype MemcachedSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of Memcached instances\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\t// +kubebuilder:validation:Minimum=1\n\t// +kubebuilder:validation:Maximum=3\n\t// +kubebuilder:validation:ExclusiveMaximum=false\n\t// +optional\n\tSize *int32 `json:\"size,omitempty\"`\n}\n\n// MemcachedStatus defines the observed state of Memcached.\ntype MemcachedStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Memcached resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Memcached is the Schema for the memcacheds API\ntype Memcached struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Memcached\n\t// +required\n\tSpec MemcachedSpec `json:\"spec\"`\n\n\t// status defines the observed state of Memcached\n\t// +optional\n\tStatus MemcachedStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// MemcachedList contains a list of Memcached\ntype MemcachedList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Memcached `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Memcached{}, &MemcachedList{})\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/api/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Memcached) DeepCopyInto(out *Memcached) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached.\nfunc (in *Memcached) DeepCopy() *Memcached {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Memcached)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Memcached) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedList) DeepCopyInto(out *MemcachedList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Memcached, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList.\nfunc (in *MemcachedList) DeepCopy() *MemcachedList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MemcachedList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) {\n\t*out = *in\n\tif in.Size != nil {\n\t\tin, out := &in.Size, &out.Size\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec.\nfunc (in *MemcachedSpec) DeepCopy() *MemcachedSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus.\nfunc (in *MemcachedStatus) DeepCopy() *MemcachedStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tcachev1alpha1 \"example.com/memcached/api/v1alpha1\"\n\t\"example.com/memcached/internal/controller\"\n\t// +kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(cachev1alpha1.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n// nolint:gocyclo\nfunc main() {\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"4b13cc52.example.com\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := (&controller.MemcachedReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Memcached\")\n\t\tos.Exit(1)\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.cache.example.com\nspec:\n  group: cache.example.com\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              size:\n                description: |-\n                  size defines the number of Memcached instances\n                  The following markers will use OpenAPI v3 schema to validate the value\n                  More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n                format: int32\n                maximum: 3\n                minimum: 1\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/cache.example.com_memcacheds.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\n#configurations:\n#- kustomizeconfig.yaml\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n#- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- path: manager_webhook_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\n#replacements:\n# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have any webhook\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.name # Name of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.namespace # Namespace of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert # This name should match the one in certificate.yaml\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- memcached_admin_role.yaml\n- memcached_editor_role.yaml\n- memcached_viewer_role.yaml\n\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/memcached_admin_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over cache.example.com.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-admin-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/memcached_editor_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the cache.example.com.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-editor-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/memcached_viewer_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to cache.example.com resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-viewer-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml",
    "content": "apiVersion: cache.example.com/v1alpha1\nkind: Memcached\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- cache_v1alpha1_memcached.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/.helmignore",
    "content": "# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/Chart.yaml",
    "content": "apiVersion: v2\nname: project\ndescription: A Helm chart to distribute project\ntype: application\n\nversion: 0.1.0\nappVersion: \"0.1.0\"\n\nkeywords:\n  - kubernetes\n  - operator\n\nannotations:\n  kubebuilder.io/generated-by: kubebuilder\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/NOTES.txt",
    "content": "Thank you for installing {{ .Chart.Name }}.\n\nYour release is named {{ .Release.Name }}.\n\nThe controller and CRDs have been installed in namespace {{ .Release.Namespace }}.\n\nTo verify the installation:\n\n  kubectl get pods -n {{ .Release.Namespace }}\n  kubectl get customresourcedefinitions\n\nTo learn more about the release, try:\n\n  $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}\n  $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"project.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"project.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nNamespace for generated references.\nAlways uses the Helm release namespace.\n*/}}\n{{- define \"project.namespaceName\" -}}\n{{- .Release.Namespace }}\n{{- end }}\n\n{{/*\nResource name with proper truncation for Kubernetes 63-character limit.\nTakes a dict with:\n  - .suffix: Resource name suffix (e.g., \"metrics\", \"webhook\")\n  - .context: Template context (root context with .Values, .Release, etc.)\nDynamically calculates safe truncation to ensure total name length <= 63 chars.\n*/}}\n{{- define \"project.resourceName\" -}}\n{{- $fullname := include \"project.fullname\" .context }}\n{{- $suffix := .suffix }}\n{{- $maxLen := sub 62 (len $suffix) | int }}\n{{- if gt (len $fullname) $maxLen }}\n{{- printf \"%s-%s\" (trunc $maxLen $fullname | trimSuffix \"-\") $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" $fullname $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/crd/memcacheds.cache.example.com.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.cache.example.com\nspec:\n  group: cache.example.com\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              size:\n                description: |-\n                  size defines the number of Memcached instances\n                  The following markers will use OpenAPI v3 schema to validate the value\n                  More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n                format: int32\n                maximum: 3\n                minimum: 1\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/manager/manager.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.manager.replicas }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: {{ include \"project.name\" . }}\n        helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n        app.kubernetes.io/managed-by: {{ .Release.Service }}\n        control-plane: controller-manager\n    spec:\n      {{- with .Values.manager.tolerations }}\n      tolerations: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.affinity }}\n      affinity: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      containers:\n      - args:\n        {{- if .Values.metrics.enable }}\n        - --metrics-bind-address=:{{ .Values.metrics.port }}\n        {{- else }}\n        # Bind to :0 to disable the controller-runtime managed metrics server\n        - --metrics-bind-address=0\n        {{- end }}\n        - --health-probe-bind-address=:8081\n        {{- range .Values.manager.args }}\n        - {{ . }}\n        {{- end }}\n        command:\n        - /manager\n        image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"\n        imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports: []\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          {{- if .Values.manager.resources }}\n          {{- toYaml .Values.manager.resources | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        securityContext:\n          {{- if .Values.manager.securityContext }}\n          {{- toYaml .Values.manager.securityContext | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        volumeMounts: []\n      securityContext:\n        {{- if .Values.manager.podSecurityContext }}\n        {{- toYaml .Values.manager.podSecurityContext | nindent 8 }}\n        {{- else }}\n        {}\n        {{- end }}\n      serviceAccountName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n      terminationGracePeriodSeconds: 10\n      volumes: []\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/metrics/controller-manager-metrics-service.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: https\n    port: {{ .Values.metrics.port }}\n    protocol: TCP\n    targetPort: {{ .Values.metrics.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/monitoring/servicemonitor.yaml",
    "content": "{{- if .Values.prometheus.enable }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-monitor\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        {{- if .Values.certManager.enable }}\n        serverName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n        # Apply secure TLS configuration with cert-manager\n        insecureSkipVerify: false\n        ca:\n          secret:\n            name: metrics-server-cert\n            key: ca.crt\n        cert:\n          secret:\n            name: metrics-server-cert\n            key: tls.crt\n        keySecret:\n          name: metrics-server-cert\n          key: tls.key\n        {{- else }}\n        # Development/Test mode (insecure configuration)\n        insecureSkipVerify: true\n        {{- end }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/controller-manager.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/leader-election-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/leader-election-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-rolebinding\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/memcached-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"memcached-admin-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/memcached-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"memcached-editor-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/memcached-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"memcached-viewer-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/metrics-auth-role.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/metrics-auth-rolebinding.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/templates/rbac/metrics-reader.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-reader\" \"context\" $) }}\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/chart/values.yaml",
    "content": "## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: controller\n    tag: latest\n    pullPolicy: IfNotPresent\n\n  ## Arguments\n  ##\n  args:\n    - --leader-elect\n\n  ## Environment variables\n  ##\n  env: []\n\n  ## Env overrides (--set manager.envOverrides.VAR=value)\n  ## Same name in env above: this value takes precedence.\n  ##\n  envOverrides: {}\n\n  ## Image pull secrets\n  ##\n  imagePullSecrets: []\n\n  ## Pod-level security settings\n  ##\n  podSecurityContext:\n    runAsNonRoot: true\n    seccompProfile:\n      type: RuntimeDefault\n\n  ## Container-level security settings\n  ##\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n      drop:\n      - ALL\n    readOnlyRootFilesystem: true\n\n  ## Resource limits and requests\n  ##\n  resources:\n    limits:\n      cpu: 500m\n      memory: 128Mi\n    requests:\n      cpu: 10m\n      memory: 64Mi\n\n  ## Manager pod's affinity\n  ##\n  affinity: {}\n\n  ## Manager pod's node selector\n  ##\n  nodeSelector: {}\n\n  ## Manager pod's tolerations\n  ##\n  tolerations: []\n\n## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: 8443\n\n## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: false\n\n## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.cache.example.com\nspec:\n  group: cache.example.com\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              size:\n                description: |-\n                  size defines the number of Memcached instances\n                  The following markers will use OpenAPI v3 schema to validate the value\n                  More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n                format: int32\n                maximum: 3\n                minimum: 1\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-role\n  namespace: project-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-manager-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-memcached-admin-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-memcached-editor-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-memcached-viewer-role\nrules:\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cache.example.com\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-rolebinding\n  namespace: project-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager-metrics-service\n  namespace: project-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager\n  namespace: project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        command:\n        - /manager\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports: []\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts: []\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes: []\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/go.mod",
    "content": "module example.com/memcached\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.27.2\n\tgithub.com/onsi/gomega v1.38.2\n\tk8s.io/api v0.35.0\n\tk8s.io/apimachinery v0.35.0\n\tk8s.io/client-go v0.35.0\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/spf13/cobra v1.10.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.36.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.5.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect\n\tgoogle.golang.org/grpc v1.72.2 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.0 // indirect\n\tk8s.io/apiserver v0.35.0 // indirect\n\tk8s.io/component-base v0.35.0 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=\ngithub.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=\ngithub.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=\ngithub.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=\ngithub.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=\ngithub.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=\ngithub.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=\ngithub.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=\ngithub.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=\ngithub.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=\ngithub.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=\ngithub.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=\ngithub.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=\ngithub.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0=\ngithub.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE=\ngithub.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=\ngithub.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=\ngo.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=\ngo.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=\ngo.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=\ngo.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=\ngo.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=\ngo.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=\ngo.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=\ngo.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=\ngo.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=\ngoogle.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=\nk8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=\nk8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=\nk8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=\nk8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=\nk8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=\nk8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=\nk8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=\nk8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=\nk8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=\nk8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=\nsigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=\nsigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcachev1alpha1 \"example.com/memcached/api/v1alpha1\"\n)\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableMemcached represents the status of the Deployment reconciliation\n\ttypeAvailableMemcached = \"Available\"\n)\n\n// MemcachedReconciler reconciles a Memcached object\ntype MemcachedReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the Memcached instance\n\t// The purpose is check if the Custom Resource for the Kind Memcached\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tmemcached := &cachev1alpha1.Memcached{}\n\terr := r.Get(ctx, req.NamespacedName, memcached)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Memcached resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get memcached\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Let's just set the status as Unknown when no status is available\n\tif len(memcached.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the memcached Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForMemcached(memcached)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Memcached\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-trigged again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif memcached.Spec.Size != nil {\n\t\tdesiredReplicas = *memcached.Spec.Size\n\t}\n\n\t// The CRD API defines that the Memcached type have a MemcachedSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", memcached.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&cachev1alpha1.Memcached{}).\n\t\tOwns(&appsv1.Deployment{}).\n\t\tNamed(\"memcached\").\n\t\tComplete(r)\n}\n\n// deploymentForMemcached returns a Memcached Deployment object\nfunc (r *MemcachedReconciler) deploymentForMemcached(\n\tmemcached *cachev1alpha1.Memcached) (*appsv1.Deployment, error) {\n\timage := \"memcached:1.6.26-alpine3.19\"\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      memcached.Name,\n\t\t\tNamespace: memcached.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: memcached.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\"app.kubernetes.io/name\": \"project\"},\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: map[string]string{\"app.kubernetes.io/name\": \"project\"},\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"memcached\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tRunAsUser:                ptr.To(int64(1001)),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"--memory-limit=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcachev1alpha1 \"example.com/memcached/api/v1alpha1\"\n)\n\nvar _ = Describe(\"Memcached Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tmemcached := &cachev1alpha1.Memcached{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Memcached\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, memcached)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &cachev1alpha1.Memcached{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: cachev1alpha1.MemcachedSpec{\n\t\t\t\t\t\tSize: ptr.To(int32(1)),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &cachev1alpha1.Memcached{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Memcached\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &MemcachedReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Memcached instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(memcached.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableMemcached)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableMemcached)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/internal/controller/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tcachev1alpha1 \"example.com/memcached/api/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = cachev1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"example.com/memcached/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n})\n\nvar _ = AfterSuite(func() {\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"example.com/memcached/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "docs/book/src/getting-started/testdata/project/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/src/getting-started.md",
    "content": "# Getting Started\n\nWe will create a sample project to let you know how it works. This sample will:\n\n- Reconcile a Memcached CR - which represents an instance of a Memcached deployed/managed on cluster\n- Create a Deployment with the Memcached image\n- Not allow more instances than the size defined in the CR which will be applied\n- Update the Memcached CR status\n\n<aside class=\"note\">\n<h1>Why Operators?</h1>\n\nBy following the [Operator Pattern][k8s-operator-pattern], it’s possible not only to provide all expected resources\nbut also to manage them dynamically, programmatically, and at execution time. To illustrate this idea, imagine if\nsomeone accidentally changed a configuration or removed a resource by mistake; in this case, the operator could fix it\nwithout any human intervention.\n\n</aside>\n\n<aside class=\"note\">\n<h1>Following Along vs Jumping Ahead</h1>\n\nNote that most of this tutorial is generated from literate Go files that\nform a runnable project, and live in the book source directory:\n[docs/book/src/getting-started/testdata/project][tutorial-source].\n\n[tutorial-source]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/cronjob-tutorial/testdata/project\n\n</aside>\n\n## Create a project\n\nFirst, create and navigate into a directory for your project. Then, initialize it using `kubebuilder`:\n\n```shell\nmkdir $GOPATH/memcached-operator\ncd $GOPATH/memcached-operator\nkubebuilder init --domain=example.com\n```\n\n<aside class=\"note\">\n<h1>Developing in $GOPATH</h1>\n\nIf your project is initialized within [`GOPATH`][GOPATH-golang-docs], the implicitly called `go mod init` will interpolate the module path for you.\nOtherwise `--repo=<module path>` must be set.\n\nRead the [Go modules blogpost][go-modules-blogpost] if unfamiliar with the module system.\n\n</aside>\n\n## Create the Memcached API (CRD):\n\nNext, we'll create the API which will be responsible for deploying and\nmanaging Memcached(s) instances on the cluster.\n\n```shell\nkubebuilder create api --group cache --version v1alpha1 --kind Memcached\n```\n\n### Understanding APIs\n\nThis command's primary aim is to produce the Custom Resource (CR) and Custom Resource Definition (CRD) for the Memcached Kind.\nIt creates the API with the group `cache.example.com` and version `v1alpha1`, uniquely identifying the new CRD of the Memcached Kind.\nBy leveraging the Kubebuilder tool, we can define our APIs and objects representing our solutions for these platforms.\n\nWhile we've added only one Kind of resource in this example, we can have as many `Groups` and `Kinds` as necessary.\nTo make it easier to understand, think of CRDs as the definition of our custom Objects, while CRs are instances of them.\n\n<aside class=\"note\">\n<h1> Please ensure that you check </h1>\n\n[Groups and Versions and Kinds, oh my!][group-kind-oh-my].\n\n</aside>\n\n### Defining our API\n\n#### Defining the Specs\n\nNow, we will define the values that each instance of your Memcached resource on the cluster can assume. In this example,\nwe will allow configuring the number of instances with the following:\n\n```go\ntype MemcachedSpec struct {\n\t...\n\t// +kubebuilder:validation:Minimum=0\n\t// +required\n\tSize *int32 `json:\"size,omitempty\"`\n}\n```\n\n#### Creating Status definitions\n\nWe also want to track the status of our Operations which will be done to manage the Memcached CR(s).\nThis allows us to verify the Custom Resource's description of our own API and determine if everything\noccurred successfully or if any errors were encountered,\nsimilar to how we do with any resource from the Kubernetes API.\n\n```go\n// MemcachedStatus defines the observed state of Memcached\ntype MemcachedStatus struct {\n    // +listType=map\n    // +listMapKey=type\n    // +optional\n    Conditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n```\n\n<aside class=\"note\">\n<h1> Status Conditions </h1>\n\nKubernetes has established conventions, and because of this, we use\nStatus Conditions here. We want our custom APIs and controllers to behave\nlike Kubernetes resources and their controllers, following these standards\nto ensure a consistent and intuitive experience.\n\nPlease ensure that you review: [Kubernetes API Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties)\n</aside>\n\n\n#### Markers and validations\n\nFurthermore, we want to validate the values added in our CustomResource\nto ensure that those are valid. To achieve this, we will use [markers][markers],\nsuch as `+kubebuilder:validation:Minimum=1`.\n\nNow, see our example fully completed.\n\n{{#literatego ./getting-started/testdata/project/api/v1alpha1/memcached_types.go}}\n\n#### Generating manifests with the specs and validations\n\nTo generate all required files:\n\n1. Run `make generate` to create the DeepCopy implementations in `api/v1alpha1/zz_generated.deepcopy.go`.\n\n2. Then, run `make manifests` to generate the CRD manifests under `config/crd/bases` and a sample for it under `config/samples`.\n\nBoth commands use [controller-gen][controller-gen] with different flags for code and manifest generation, respectively.\n\n<details><summary><code>config/crd/bases/cache.example.com_memcacheds.yaml</code>: Our Memcached CRD</summary>\n\n```yaml\n{{#include ./getting-started/testdata/project/config/crd/bases/cache.example.com_memcacheds.yaml}}\n```\n\n</details>\n\n#### Sample of Custom Resources\n\nThe manifests located under the `config/samples` directory serve as examples of Custom Resources that can be applied to the cluster.\nIn this particular example, by applying the given resource to the cluster, we would generate\na Deployment with a single instance size (see `size: 1`).\n\n```yaml\n{{#include ./getting-started/testdata/project/config/samples/cache_v1alpha1_memcached.yaml}}\n```\n\n### Reconciliation Process\n\nIn a simplified way, Kubernetes works by allowing us to declare the desired state of our system, and then its controllers continuously observe the cluster and take actions to ensure that the actual state matches the desired state. For our custom APIs and controllers, the process is similar. Remember, we are extending Kubernetes' behaviors and its APIs to fit our specific needs.\n\nIn our controller, we will implement a reconciliation process.\n\nEssentially, the reconciliation process functions as a loop, continuously checking conditions and performing necessary actions until the desired state is achieved. This process will keep running until all conditions in the system align with the desired state defined in our implementation.\n\nHere's a pseudo-code example to illustrate this:\n\n```go\nreconcile App {\n\n  // Check if a Deployment for the app exists, if not, create one\n  // If there's an error, then restart from the beginning of the reconcile\n  if err != nil {\n    return reconcile.Result{}, err\n  }\n\n  // Check if a Service for the app exists, if not, create one\n  // If there's an error, then restart from the beginning of the reconcile\n  if err != nil {\n    return reconcile.Result{}, err\n  }\n\n  // Look for Database CR/CRD\n  // Check the Database Deployment's replicas size\n  // If deployment.replicas size doesn't match cr.size, then update it\n  // Then, restart from the beginning of the reconcile. For example, by returning `reconcile.Result{Requeue: true}, nil`.\n  if err != nil {\n    return reconcile.Result{Requeue: true}, nil\n  }\n  ...\n\n  // If at the end of the loop:\n  // Everything was executed successfully, and the reconcile can stop\n  return reconcile.Result{}, nil\n\n}\n```\n\n<aside class=\"note\">\n<h1> Return Options </h1>\n\nThe following are a few possible return options to restart the Reconcile:\n\n- With the error:\n\n```go\nreturn ctrl.Result{}, err\n```\n- Without an error:\n\n```go\nreturn ctrl.Result{Requeue: true}, nil\n```\n\n- Therefore, to stop the Reconcile, use:\n\n```go\nreturn ctrl.Result{}, nil\n```\n\n- Reconcile again after X time:\n\n```go\nreturn ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())}, nil\n```\n\n</aside>\n\n#### In the context of our example\n\nWhen our sample Custom Resource (CR) is applied to the cluster (i.e. `kubectl apply -f config/sample/cache_v1alpha1_memcached.yaml`),\nwe want to ensure that a Deployment is created for our Memcached image and that it matches the number of replicas defined in the CR.\n\nTo achieve this, we need to first implement an operation that checks whether the Deployment for our Memcached instance already exists on the cluster.\nIf it does not, the controller will create the Deployment accordingly. Therefore, our reconciliation process must include an operation to ensure that\nthis desired state is consistently maintained. This operation would involve:\n\n```go\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep := r.deploymentForMemcached()\n\t\t// Create the Deployment on the cluster\n\t\tif err = r.Create(ctx, dep); err != nil {\n            log.Error(err, \"Failed to create new Deployment\",\n            \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n            return ctrl.Result{}, err\n        }\n\t\t...\n\t}\n```\n\nNext, note that the `deploymentForMemcached()` function will need to define and return the Deployment that should be\ncreated on the cluster. This function should construct the Deployment object with the necessary\nspecifications, as demonstrated in the following example:\n\n```go\n    dep := &appsv1.Deployment{\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: &replicas,\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           \"memcached:1.6.26-alpine3.19\",\n\t\t\t\t\t\tName:            \"memcached\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"--memory-limit=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n```\n\nAdditionally, we need to implement a mechanism to verify that the number of Memcached replicas\non the cluster matches the desired count specified in the Custom Resource (CR). If there is a\ndiscrepancy, the reconciliation must update the cluster to ensure consistency. This means that\nwhenever a CR of the Memcached Kind is created or updated on the cluster, the controller will\ncontinuously reconcile the state until the actual number of replicas matches the desired count.\nThe following example illustrates this process:\n\n```go\n\t...\n\tsize := memcached.Spec.Size\n\tif *found.Spec.Replicas != size {\n\t\tfound.Spec.Replicas = &size\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n            return ctrl.Result{}, err\n        }\n    ...\n```\n\nNow, you can review the complete controller responsible for managing Custom Resources of the\nMemcached Kind. This controller ensures that the desired state is maintained in the cluster,\nmaking sure that our Memcached instance continues running with the number of replicas specified\nby the users.\n\n<details><summary><code>internal/controller/memcached_controller.go</code>: Our Controller Implementation </summary>\n\n```go\n{{#include ./getting-started/testdata/project/internal/controller/memcached_controller.go}}\n```\n</details>\n\n### Diving Into the Controller Implementation\n\n#### Setting Manager to Watching Resources\n\nThe whole idea is to be Watching the resources that matter for the controller.\nWhen a resource that the controller is interested in changes, the Watch triggers the controller's\nreconciliation loop, ensuring that the actual state of the resource matches the desired state\nas defined in the controller's logic.\n\nNotice how we configured the Manager to monitor events such as the creation, update, or deletion of a Custom Resource (CR) of the Memcached kind,\nas well as any changes to the Deployment that the controller manages and owns:\n\n```go\n// SetupWithManager sets up the controller with the Manager.\n// The Deployment is also watched to ensure its\n// desired state in the cluster.\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {\n    return ctrl.NewControllerManagedBy(mgr).\n\t\t// Watch the Memcached Custom Resource and trigger reconciliation whenever it\n\t\t//is created, updated, or deleted\n\t\tFor(&cachev1alpha1.Memcached{}).\n\t\t// Watch the Deployment managed by the Memcached controller. If any changes occur to the Deployment\n        // owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n        // state aligns with the desired state.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n    }\n```\n\n#### But, How Does the Manager Know Which Resources Are Owned by It?\n\nWe do not want our Controller to watch any Deployment on the cluster and trigger our\nreconciliation loop. Instead, we only want to trigger reconciliation when the specific\nDeployment running our Memcached instance is changed. For example,\nif someone accidentally deletes our Deployment or changes the number of replicas, we want\nto trigger the reconciliation to ensure that it returns to the desired state.\n\nThe Manager knows which Deployment to observe because we set the `ownerRef` (Owner Reference):\n\n```go\nif err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil {\n    return nil, err\n}\n```\n\n<aside class=\"note\">\n\n<h1><code>ownerRef</code> and Cascading Events</h1>\n\nThe ownerRef is crucial not only for allowing us to observe changes on the specific resource but also because,\nif we delete the Memcached Custom Resource (CR) from the cluster, we want all resources owned by it to be automatically\ndeleted as well, in a cascading event.\n\nThis ensures that when the parent resource (Memcached CR) is removed, all associated resources\n(like Deployments, Services, etc.) are also cleaned up, maintaining\na tidy and consistent cluster state.\n\nFor more information, see the Kubernetes documentation on [Owners and Dependents](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/).\n\n</aside>\n\n### Granting Permissions\n\nIt's important to ensure that the Controller has the necessary permissions(i.e. to create, get, update, and list)\nthe resources it manages.\n\nThe [RBAC permissions][k8s-rbac] are now configured via [RBAC markers][rbac-markers], which are used to generate and update the\nmanifest files present in `config/rbac/`. These markers can be found (and should be defined) on the `Reconcile()` method of each controller, see\nhow it is implemented in our example:\n\n```go\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n```\n\nAfter making changes to the controller, run the make manifests command. This will prompt [controller-gen][controller-gen]\nto refresh the files located under `config/rbac`.\n\n<details><summary><code>config/rbac/role.yaml</code>: Our RBAC Role generated </summary>\n\n```yaml\n{{#include ./getting-started/testdata/project/config/rbac/role.yaml}}\n```\n</details>\n\n### Manager (main.go)\n\nThe [Manager][manager] in the `cmd/main.go` file is responsible for managing the controllers in your application.\n\n<details><summary><code>cmd/main.go</code>: Our main.go </summary>\n\n```go\n{{#include ./getting-started/testdata/project/cmd/main.go}}\n```\n</details>\n\n### Use Kubebuilder plugins to scaffold additional options\n\nNow that you have a better understanding of how to create your own API and controller,\nlet’s scaffold in this project the plugin [`autoupdate.kubebuilder.io/v1-alpha`][autoupdate-plugin]\nso that your project can be kept up to date with the latest Kubebuilder releases scaffolding changes\nand consequently adopt improvements from the ecosystem.\n\n```shell\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\"\n```\n\nInspect the file `.github/workflows/auto-update.yml` to see how it works.\n\n### Checking the Project running in the cluster\n\nAt this point you can check the steps to validate the project\non the cluster by looking the steps defined in the Quick Start,\nsee: [Run It On the Cluster](./quick-start#run-it-on-the-cluster)\n\n## Next Steps\n\n- To delve deeper into developing your solution, consider going through the [CronJob Tutorial][cronjob-tutorial]\n- For insights on optimizing your approach, refer to the [Best Practices][best-practices] documentation.\n\n<aside class=\"note\">\n<h1> Using Deploy Image plugin to generate APIs and source code </h1>\n\nNow that you have a better understanding, you might want to check out the [Deploy Image][deploy-image] Plugin.\nThis plugin allows users to scaffold APIs/Controllers to deploy and manage an Operand (image) on the cluster.\nIt will provide scaffolds similar to the ones in this guide, along with additional features such as tests\nimplemented for your controller.\n\n</aside>\n\n[k8s-operator-pattern]: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[group-kind-oh-my]: ./cronjob-tutorial/gvks.md\n[controller-gen]: ./reference/controller-gen.md\n[markers]: ./reference/markers.md\n[rbac-markers]: ./reference/markers/rbac.md\n[k8s-rbac]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/\n[manager]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager\n[options-manager]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager#Options\n[quick-start]: ./quick-start.md\n[best-practices]: ./reference/good-practices.md\n[cronjob-tutorial]: https://book.kubebuilder.io/cronjob-tutorial/cronjob-tutorial.html\n[deploy-image]: ./plugins/available/deploy-image-plugin-v1-alpha.md\n[GOPATH-golang-docs]: https://golang.org/doc/code.html#GOPATH\n[go-modules-blogpost]: https://blog.golang.org/using-go-modules\n[autoupdate-plugin]: ./plugins/available/autoupdate-v1-alpha.md"
  },
  {
    "path": "docs/book/src/introduction.md",
    "content": ">[!Tip]\n>Impatient readers may head straight to [Quick Start](quick-start.md).\n\n>[!Important]\n>Using previous version of Kubebuilder? Check the legacy documentation for [v1](https://book-v1.book.kubebuilder.io), [v2](https://book-v2.book.kubebuilder.io) or [v3](https://book-v3.book.kubebuilder.io).\n\n## Who is this for\n\n#### Users of Kubernetes\n\nUsers of Kubernetes will develop a deeper understanding of Kubernetes through learning\nthe fundamental concepts behind how APIs are designed and implemented.  This book\nwill teach readers how to develop their own Kubernetes APIs and the\nprinciples from which the core Kubernetes APIs are designed.\n\nIncluding:\n\n- The structure of Kubernetes APIs and Resources\n- API versioning semantics\n- Self-healing\n- Garbage Collection and Finalizers\n- Declarative vs Imperative APIs\n- Level-Based vs Edge-Base APIs\n- Resources vs Subresources\n\n#### Kubernetes API extension developers\n\nAPI extension developers will learn the principles and concepts behind implementing canonical\nKubernetes APIs, as well as simple tools and libraries for rapid execution.  This\nbook covers pitfalls and misconceptions that extension developers commonly encounter.\n\nIncluding:\n\n- How to batch multiple events into a single reconciliation call\n- How to configure periodic reconciliation\n- *Forthcoming*\n    - When to use the lister cache vs live lookups\n    - Garbage Collection vs Finalizers\n    - How to use Declarative vs Webhook Validation\n    - How to implement API versioning\n\n## Why Kubernetes APIs\n\nKubernetes APIs provide consistent and well defined endpoints for\nobjects adhering to a consistent and rich structure.\n\nThis approach has fostered a rich ecosystem of tools and libraries for working\nwith Kubernetes APIs.\n\nUsers work with the APIs through declaring objects as *yaml* or *json* config, and using\ncommon tooling to manage the objects.\n\nBuilding services as Kubernetes APIs provides many advantages to plain old REST, including:\n\n* Hosted API endpoints, storage, and validation.\n* Rich tooling and CLIs such as `kubectl` and `kustomize`.\n* Support for AuthN and granular AuthZ.\n* Support for API evolution through API versioning and conversion.\n* Facilitation of adaptive / self-healing APIs that continuously respond to changes\n  in the system state without user intervention.\n* Kubernetes as a hosting environment\n\nDevelopers may build and publish their own Kubernetes APIs for installation into\nrunning Kubernetes clusters.\n\n## Contribution\n\nIf you like to contribute to either this book or the code, please be so kind\nto read our [Contribution](https://github.com/kubernetes-sigs/kubebuilder/blob/master/CONTRIBUTING.md) guidelines first.\n\n## Resources\n\n* Repository: [sigs.k8s.io/kubebuilder](https://sigs.k8s.io/kubebuilder)\n\n* Slack channel: [#kubebuilder](http://slack.k8s.io/#kubebuilder)\n\n* Google Group:\n  [kubebuilder@googlegroups.com](https://groups.google.com/forum/#!forum/kubebuilder)\n"
  },
  {
    "path": "docs/book/src/logos/README.md",
    "content": "# Kubebuilder Logos\n\nThe official location for the logos is in a [public GCS\nbucket][kb-logos-gcs] (or if you like GCS XML listings,\n[here][kb-logos-gcs-direct]).\n\nThese logos are copies used in the book, resized to their appropriate\nsizes.\n\n[kb-logos-gcs]: https://console.cloud.google.com/storage/browser/kubebuilder-logos\n\n[kb-logos-gcs-direct]: https://storage.googleapis.com/kubebuilder-logos\n"
  },
  {
    "path": "docs/book/src/migration/ai-helpers.md",
    "content": "# Using AI to Migrate Projects from Any Version to the Latest\n\nAI can assist manual migrations by reducing repetitive work and helping resolve breaking changes. It won't replace the [Manual Migration Process](./manual-process.md), but it can help reduce effort and accomplish the goal.\n\n<aside class=\"warning\">\n\n<h1>Important</h1>\n\nThese AI instructions are provided as examples to help guide your migration. Always validate AI output carefully - you remain responsible for ensuring correctness.\n\n</aside>\n\n## Workflow and AI-Assisted Steps\n\n**Step 1: Reorganize to New Layout** (required only for legacy layouts)\n\nAI helps ensure the project is structured with the new layout (main.go under cmd/, controllers and webhooks inside internal/). Review and verify the reorganization, then run `make build` to ensure it still compiles.\n\nSee [Step 1: Reorganize to New Layout](./reorganize-layout.md)\n\n**Step 2: Discovery CLI Commands to Re-scaffold**\n\nAI analyzes your project and generates all Kubebuilder CLI commands to fully re-scaffold with the latest release. Create a backup (`mkdir ../migration-backup && cp -r . ../migration-backup/`), then execute the generated commands to scaffold a fresh project.\n\nSee [Step 2: Discovery CLI Commands](./discovery-commands.md)\n\n**Step 3: Port Custom Code**\n\nAI helps port your custom code from backup to the new scaffolded project. Review all changes carefully and ensure business logic is correctly transferred.\n\nSee [Step 3: Port Custom Code](./port-code.md)\n\n**Step 4: Validate**\n\nRun `make generate && make manifests && make build`, then `make test` to verify all tests pass. Deploy to a test cluster and verify your solution still does the same thing.\n\nSee the [Manual Migration Process](./manual-process.md) for complete details.\n"
  },
  {
    "path": "docs/book/src/migration/discovery-commands.md",
    "content": "# Step 2: Discovery CLI Commands\n\nUse AI to analyze your (now reorganized) Kubebuilder project and generate all CLI commands needed to recreate it with the latest version.\n\n<aside class=\"note\">\n\n<h1>You May Not Need This</h1>\n\n**If you have a PROJECT file** and used Kubebuilder CLI to scaffold **all** resources (APIs, controllers, webhooks), you can use `kubebuilder alpha generate` instead.\n\nThe `alpha generate` command re-scaffolds everything tracked in your PROJECT file automatically. See the [alpha generate documentation](../reference/commands/alpha_generate.md) for details.\n\n**Use this AI discovery step if:**\n- You don't have a PROJECT file (Kubebuilder < v3.0.0)\n- You manually created some APIs, controllers, or webhooks (not tracked in PROJECT file)\n- You want to verify all resources are discovered\n\n</aside>\n\n<aside class=\"note\">\n\n<h1>When to Use This</h1>\n\nUse AI discovery if your project has:\n- APIs not tracked in the PROJECT file (manually created)\n- Controllers for external types (Deployments, Pods, cert-manager resources)\n- Multiple versions of the same Kind\n- Complex webhook configurations\n\nAI scans your entire codebase to discover everything, ensuring nothing is missed.\n\n</aside>\n\n## Instructions to provide to your AI assistant\n\n<aside class=\"warning\">\n\n<h1>Standard Kubebuilder Layout Only</h1>\n\nThese instructions work for projects using **standard Kubebuilder directory layout**:\n- API types in `api/` directory (some projects use `apis/`)\n- Controllers in `controllers/`, `internal/controller/`, or `pkg/controllers/`\n- Standard file naming: `<kind>_types.go`, `<kind>_controller.go`\n\nProjects with heavily customized layouts may require manual analysis.\n\n</aside>\n\nCopy and paste these instructions to your AI assistant (Cursor, Claude, GitHub Copilot, etc.):\n\n```\nAnalyze this Kubebuilder project and generate all CLI commands to recreate it.\n\nCONTEXT:\nKubebuilder projects have these components:\n\nAPIs (Custom Resources):\n- Location: api/ or apis/ directory\n- Recognition: Look for Go structs with marker: // +kubebuilder:object:root=true\n- Pattern: type <Name> struct with metav1.TypeMeta and metav1.ObjectMeta fields\n- Example: type Captain struct { metav1.TypeMeta; metav1.ObjectMeta; Spec CaptainSpec; Status CaptainStatus }\n\nControllers:\n- Location: controllers/, internal/controller/, or pkg/controllers/\n- Recognition: Look for Reconcile() function signature\n- Pattern: func (r *<Name>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)\n- Struct embeds: client.Client\n- Has function: SetupWithManager(mgr ctrl.Manager) error\n\nWebhooks:\n- Location: api/v1/ or internal/webhook/v1/\n- Recognition: Look for webhook method signatures\n- Defaulting pattern: func Default() or func Default(ctx context.Context, obj *<Type>) error\n- Validation pattern: func ValidateCreate() error or func ValidateCreate(ctx context.Context, obj *<Type>) (admission.Warnings, error)\n- Conversion pattern: func Hub() or func ConvertTo() or func ConvertFrom()\n\nCLI Command Formats:\n- kubebuilder init --domain <domain> --repo <module>\n- kubebuilder edit --multigroup=true (if multi-group layout)\n- kubebuilder create api --group <group> --version <version> --kind <Kind> --controller=<bool> --resource=<bool>\n  * --controller=true: create controller\n  * --resource=true: create API definition\n  * --resource=false: controller only (for external types like Deployment, Pod)\n- kubebuilder create webhook --group <group> --version <version> --kind <Kind> [flags]\n  * --defaulting: sets default values\n  * --programmatic-validation: validates create/update/delete\n  * --conversion --spoke <versions>: for multi-version APIs (hub-spoke pattern)\n    - Hub version: Usually oldest stable version (e.g., v1) - command runs on this version\n    - Spoke versions: Newer versions that convert to/from hub (e.g., v2, v3) - specified with --spoke\n    - Example: --group crew --version v1 --kind Captain --conversion --spoke v2\n      (v1 is hub, v2 is spoke)\n- External types (k8s.io/api/*): use --resource=false --controller=true\n\nProject structure patterns:\n- Single-group: api/v1/, api/v2/ (versions directly under api/)\n- Multi-group: api/<group>/v1/, api/<group>/v2/ (group subdirectories)\n- Multi-group detection: Check PROJECT file for \"multigroup: true\" OR check if api/ has group subdirectories\n\nFiles to IGNORE:\n- zz_generated.*.go (auto-generated code)\n- groupversion_info.go (just group registration)\n- config/crd/bases/*.yaml (auto-generated from code)\n- config/rbac/*.yaml (auto-generated from markers)\n\nReferences:\n- Kubebuilder Book: https://book.kubebuilder.io\n- controller-runtime: https://github.com/kubernetes-sigs/controller-runtime\n- controller-tools: https://github.com/kubernetes-sigs/controller-tools\n\nANALYZE PROJECT:\n\n1. Extract module path from go.mod (line 1: \"module <path>\")\n2. Extract domain from PROJECT file (domain: <value>) OR api/*/groupversion_info.go (// +groupName=<group>.<domain>)\n3. Detect multi-group: api/ has api/<group>/v1/ structure? (yes/no)\n\n4. Scan api/ or apis/ directory - Find ALL your own APIs:\n   - Find all *_types.go files OR types.go (exclude groupversion_info.go, zz_generated.deepcopy.go)\n   - For each file, find: type <Kind> struct with // +kubebuilder:object:root=true above it\n   - Extract: Kind name, group (from groupversion_info.go +groupName comment), version (from directory)\n   - Check controller: look for controllers/<lowercaseKind>_controller.go OR internal/controller/<lowercaseKind>_controller.go OR pkg/controllers/<lowercaseKind>_controller.go\n   - Check webhooks: look for api/v1/<lowercaseKind>_webhook.go OR internal/webhook/v1/<lowercaseKind>_webhook.go\n   - If webhook file found, scan for methods:\n     * \"func (r *<Kind>) Default()\": has --defaulting\n     * \"func (r *<Kind>) ValidateCreate()\": has --programmatic-validation\n     * \"func (*<Kind>) Hub()\": this version is conversion hub\n     * \"func (r *<Kind>) ConvertTo(\": this version is a spoke\n\n5. Scan internal/controller/, controllers/, or pkg/controllers/ - Find controllers for external types:\n   - For each *_controller.go file, check imports NOT from your module\n   - Look for: k8s.io/api/apps/v1, k8s.io/api/core/v1, github.com/cert-manager/cert-manager/pkg/apis/*\n   - Extract type from: type <Kind>Reconciler struct OR Reconcile signature\n   - This is a controller-only resource (use --controller=true --resource=false)\n\n6. Scan internal/webhook/ - Find webhooks for external types:\n   - For each *_webhook.go file in internal/webhook/v1/ (or other versions)\n   - Check if the Kind type is imported (not defined in your api/)\n   - If imported from k8s.io/api/* or external package: external type webhook\n   - Scan for Default() and ValidateCreate() methods to determine flags\n\nOUTPUT FORMAT (bash script):\n\n#!/bin/bash\n# Module: <module-path>\n# Domain: <domain>\n# Multi-group: <yes/no>\n\nset -e\nkubebuilder init --domain <domain> --repo <module-path>\nkubebuilder edit --multigroup=true  # only if multi-group\n\n# External type controllers (--resource=false)\nkubebuilder create api --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=<path> --external-api-domain=<domain> --external-api-module=<module>\n\n# Your own APIs (--resource=true)\nkubebuilder create api --group crew --version v1 --kind Captain --controller=true --resource=true\nkubebuilder create api --group crew --version v2 --kind FirstMate --controller=false --resource=true\n\n# Webhooks for your own APIs\nkubebuilder create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation\nkubebuilder create webhook --group crew --version v1 --kind FirstMate --conversion --spoke v2\n\n# Webhooks for external/core types (no create api needed)\nkubebuilder create webhook --group apps --version v1 --kind Deployment --defaulting --programmatic-validation\nkubebuilder create webhook --group core --version v1 --kind Pod --defaulting\n\nmake manifests && make generate && make build\n\nRULES:\n- Combine ALL webhook types in ONE command: --defaulting --programmatic-validation together\n- Conversion webhooks: use hub version and list ALL spokes: --conversion --spoke v2,v3\n- List EVERY Kind found in source code, not just what's in PROJECT file\n- External type controllers: use --controller=true --resource=false\n- Webhooks for external/core types: just create webhook (no create api needed)\n- Order: external controllers first, then your APIs, then all webhooks\n```\n\n## Understanding the Output\n\nThe AI will analyze your project and output a bash script. The script will contain commands in this order:\n\n1. `kubebuilder init` - Initialize the project\n2. `kubebuilder edit --multigroup=true` - If multi-group detected\n3. `kubebuilder create api` - For external type controllers (with `--resource=false`)\n4. `kubebuilder create api` - For your own APIs (with `--resource=true`)\n5. `kubebuilder create webhook` - For all webhooks\n6. `make manifests && make generate && make build` - Verify\n\n## Example Outputs\n\nHere are real examples of what the AI instructions generate:\n\n### Example 1: Simple Multi-Group Project\n\nAnalyzed: [kubernetes-sigs/scheduler-plugins](https://github.com/kubernetes-sigs/scheduler-plugins)\n\n```bash\n#!/bin/bash\n# Module: sigs.k8s.io/scheduler-plugins\n# Domain: scheduling.x-k8s.io\n# Multi-group: YES\n\nset -e\nkubebuilder init --domain scheduling.x-k8s.io --repo sigs.k8s.io/scheduler-plugins\nkubebuilder edit --multigroup=true\n\nkubebuilder create api --group scheduling --version v1alpha1 --kind ElasticQuota --controller=true --resource=true\nkubebuilder create api --group scheduling --version v1alpha1 --kind PodGroup --controller=true --resource=true\n\nmake manifests && make generate && make build\n```\n\n**Discovered:** 2 APIs, multi-group, no webhooks\n\n### Example 2: Single-Group with Webhooks (go/v3 Migration)\n\nAnalyzed: [project-v3](https://github.com/kubernetes-sigs/kubebuilder/tree/release-3.13/testdata/project-v3)\n\n```bash\n#!/bin/bash\n# Module: sigs.k8s.io/kubebuilder/testdata/project-v3\n# Domain: testproject.org\n# Multi-group: NO\n\nset -e\nkubebuilder init --domain testproject.org --repo sigs.k8s.io/kubebuilder/testdata/project-v3\n\nkubebuilder create api --group crew --version v1 --kind Captain --controller=true --resource=true\nkubebuilder create api --group crew --version v1 --kind FirstMate --controller=true --resource=true\nkubebuilder create api --group crew --version v1 --kind Admiral --controller=true --resource=true\n\nkubebuilder create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation\nkubebuilder create webhook --group crew --version v1 --kind Admiral --defaulting\n\nmake manifests && make generate && make build\n```\n\n**Discovered:** 3 APIs, single-group, webhooks with defaulting and validation\n\n### Example 3: Complex Multi-Group with External Types\n\nAnalyzed: testdata/project-v4-multigroup\n\n```bash\n#!/bin/bash\n# Module: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup\n# Domain: testproject.org\n# Multi-group: YES\n\nset -e\nkubebuilder init --domain testproject.org --repo sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup\nkubebuilder edit --multigroup=true\n\n# External type controllers\nkubebuilder create api --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager@v1.19.2\nkubebuilder create api --group apps --version v1 --kind Deployment --controller=true --resource=false\n\n# APIs - Group: crew\nkubebuilder create api --group crew --version v1 --kind Captain --controller=true --resource=true\n\n# APIs - Group: ship\nkubebuilder create api --group ship --version v1beta1 --kind Frigate --controller=true --resource=true\nkubebuilder create api --group ship --version v1 --kind Destroyer --controller=true --resource=true\nkubebuilder create api --group ship --version v2alpha1 --kind Cruiser --controller=true --resource=true\n\n# APIs - Group: sea-creatures\nkubebuilder create api --group sea-creatures --version v1beta1 --kind Kraken --controller=true --resource=true\nkubebuilder create api --group sea-creatures --version v1beta2 --kind Leviathan --controller=true --resource=true\n\n# APIs - Group: foo.policy\nkubebuilder create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true\n\n# APIs - Group: foo\nkubebuilder create api --group foo --version v1 --kind Bar --controller=true --resource=true\n\n# APIs - Group: fiz\nkubebuilder create api --group fiz --version v1 --kind Bar --controller=true --resource=true\n\n# APIs - Group: example.com\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached --controller=true --resource=true\nkubebuilder create api --group example.com --version v1alpha1 --kind Busybox --controller=true --resource=true\nkubebuilder create api --group example.com --version v1 --kind Wordpress --controller=true --resource=true\nkubebuilder create api --group example.com --version v2 --kind Wordpress --controller=false --resource=true\n\n# Webhooks for your APIs\nkubebuilder create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation\nkubebuilder create webhook --group ship --version v1 --kind Destroyer --defaulting\nkubebuilder create webhook --group ship --version v2alpha1 --kind Cruiser --programmatic-validation\nkubebuilder create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation\nkubebuilder create webhook --group example.com --version v1 --kind Wordpress --conversion --spoke v2\n\n# Webhooks for external types\nkubebuilder create webhook --group cert-manager --version v1 --kind Issuer --defaulting\nkubebuilder create webhook --group core --version v1 --kind Pod --programmatic-validation\nkubebuilder create webhook --group apps --version v1 --kind Deployment --defaulting --programmatic-validation\n\nmake manifests && make generate && make build\n```\n\n**Discovered:** 12 APIs across 6 groups, conversion webhook, external controllers, external webhooks\n\n## What to Do Next\n\n1. Review the generated script carefully and ensure it matches your project structure.\n2. Save it as `migration-commands.sh` and make it executable: `chmod +x migration-commands.sh`\n3. Follow the [Manual Migration Process](./manual-process.md) to:\n   - Backup your project in another location\n   - Execute the commands of this script in the root of your project when it is empty\n   - After you have the fully re-scaffolded project, you will need to add all your code back on top of it\n   - Port your custom code"
  },
  {
    "path": "docs/book/src/migration/manual-process.md",
    "content": "# Manual Migration Process\n\nPlease ensure you have followed the [installation guide][quick-start]\nto install the required components and have the desired version of the\nKubebuilder CLI available in your `PATH`.\n\nThis guide outlines the manual steps to migrate your existing Kubebuilder\nproject to a newer version of the Kubebuilder framework. This process involves\nre-scaffolding your project and manually porting over your custom code and configurations.\n\nFrom Kubebuilder `v3.0.0` onwards, all inputs used by Kubebuilder are tracked in the [PROJECT][project-config] file.\nEnsure that you check this file in your current project to verify the recorded configuration and metadata.\nReview the [PROJECT file documentation][project-config] for a better understanding.\n\nAlso, before starting, it is recommended to check [What's in a basic project?][basic-project-doc]\nto better understand the project layouts and structure.\n\n\n<aside class=\"warning\">\n<h1>About Manual Migration</h1>\n\nManual migration is more complex than automated methods but gives you complete control. Use manual migration when:\n- Your project has significant customizations\n- Automated tools aren't available for your version yet\n\n**Two-phase approach (recommended for legacy layouts):**\n1. **Reorganize layout** - Move files to new structure (controllers → internal/controller, webhooks → internal/webhook, main.go → cmd), update imports, test, commit\n2. **Migrate to latest** - Re-scaffold with latest version, port code\n\nThis keeps your project working at each step and simplifies porting.\nAI migration helpers are provided to automate repetitive tasks.\nSee [AI Migration Helpers](./ai-helpers.md) for AI instructions that automate both phases.\n\n**For future updates:** Once migrated, use the [AutoUpdate plugin][autoupdate-plugin] or [alpha update][alpha-update] command to automatically update scaffolds with 3-way merge while preserving customizations.\n\n</aside>\n\n\n## Phase 1: Reorganize to New Layout (Required only for Legacy Layouts)\n\n**Only needed if ANY of these are true:**\n- Controllers are NOT in `internal/controller/`\n- Webhooks are NOT in `internal/webhook/`\n- Main is NOT in `cmd/`\n\n**Skip this phase if** your project already uses `internal/controller/`, `internal/webhook/`, and `cmd/main.go`.\n\n### 1.1 Create a reorganization branch\n\n```bash\ngit checkout -b reorganize\n```\n\n### 1.2 Reorganize file locations\n\n<aside class=\"note\">\n\n<h1>Skip if Using AI Migration Helper</h1>\n\nIf you used [Step 1: Reorganize to New Layout](./reorganize-layout.md) AI migration helper, your project is already reorganized. Skip to [Phase 2](#phase-2-migrate-to-latest-version).\n\n</aside>\n\nMove files to new layout:\n\n```bash\n# If you have controllers/ directory\nmkdir -p internal/controller\nmv controllers/* internal/controller/\nrmdir controllers\n\n# OR if you have pkg/controllers/ directory\nmkdir -p internal/controller\nmv pkg/controllers/* internal/controller/\n\n# If you have webhooks in api/v1/ or apis/v1/\nmkdir -p internal/webhook/v1\nmv api/v1/*_webhook* internal/webhook/v1/ 2>/dev/null || mv apis/v1/*_webhook* internal/webhook/v1/ 2>/dev/null || echo \"No webhook files found to move (this is expected if your project has no webhooks)\"\n\n# If main.go is in root\nmkdir -p cmd\nmv main.go cmd/\n```\n\n### 1.3 Update package declarations\n\nAfter moving files, update package declarations:\n\n**Controllers:** Change `package controllers` → `package controller` in all `*_controller.go` and `*_controller_test.go` files.\n\n**Webhooks:** Keep version as package name (e.g., `package v1` stays `package v1` in `internal/webhook/v1/`).\n\n### 1.4 Update import paths\n\nFind and update all imports:\n\n```bash\ngrep -r \"pkg/controllers\\|/controllers\\\"\" --include=\"*.go\"\n```\n\nIn each file found, update:\n- Imports: `<module>/controllers` or `<module>/pkg/controllers` → `<module>/internal/controller`\n- References: `controllers.TypeName` → `controller.TypeName`\n\n### 1.5 Update Dockerfile (if needed)\n\nIf your Dockerfile has explicit COPY statements for moved paths, update them to reflect the new structure, or simplify to `COPY . .` and use `.dockerignore` to exclude unnecessary files.\n\n### 1.6 Verify and commit\n\nBuild and test the reorganized project:\n\n```bash\nmake generate manifests\nmake build && make test\n```\n\nIf successful, commit the layout changes. Your project now uses the new layout. Proceed to Phase 2.\n\n## Phase 2: Migrate to Latest Version\n\n### Step 1: Prepare Your Current Project\n\n### 1.1 Create a migration branch\n\nCreate a branch from your current codebase:\n\n```bash\ngit checkout -b migration\n```\n\n### 1.2 Create a backup\n\n```bash\nmkdir ../migration-backup\ncp -r . ../migration-backup/\n```\n\n### 1.3 Clean your project directory\n\nRemove all files except `.git`:\n\n```bash\nfind . -not -path './.git*' -not -name '.' -not -name '..' -delete\n```\n\n## Step 2: Initialize the New Project\n\n**About the PROJECT file:** From v3.0.0+, the `PROJECT` file tracks all scaffolding metadata. If you have one and used CLI for all resources, try `kubebuilder alpha generate` first. Otherwise, follow the manual steps below to identify and re-scaffold all resources.\n\n### 2.1 Identify your module and domain\n\nIdentify the information you'll need for initialization from your backup.\n\n<aside class=\"note\">\n\n<h1>Skip if Using AI Migration Helper</h1>\n\nIf you used [Step 2: Discovery CLI Commands](./discovery-commands.md) AI migration helper, you already have a complete script with all commands. Execute it and skip to [Step 4: Port Your Custom Code](#step-4-port-your-custom-code).\n\n</aside>\n\n**Module path** - Check your backup's `go.mod` file:\n\n```bash\ncat ../migration-backup/go.mod\n```\n\nLook for the module line:\n\n```go\nmodule tutorial.kubebuilder.io/migration-project\n```\n\n**Domain** - Check your backup's `PROJECT` file:\n\n```bash\ncat ../migration-backup/PROJECT\n```\n\nLook for the domain line:\n\n```yaml\ndomain: tutorial.kubebuilder.io\n```\n\nIf you don't have a `PROJECT` file (versions < `v3.0.0`),\ncheck your CRD files under `config/crd/bases/` or examine the API group names.\nThe domain is the part after the group name in your API groups.\n\n### 2.2 Initialize the Go module\n\nInitialize a new Go module using the same module path from your original project:\n\n```bash\ngo mod init tutorial.kubebuilder.io/migration-project\n```\n\nReplace `tutorial.kubebuilder.io/migration-project` with your actual module path.\n\n### 2.3 Initialize Kubebuilder project\n\nInitialize the project with Kubebuilder:\n\n```bash\nkubebuilder init --domain tutorial.kubebuilder.io --repo tutorial.kubebuilder.io/migration-project\n```\n\nReplace with your actual domain and repository (module path).\n\n<aside class=\"note\">\n<h1>Understanding init options</h1>\n\n- `--domain`: The domain for your API groups (e.g., `tutorial.kubebuilder.io`). Your full API groups will be `<group>.<domain>`.\n- `--repo`: Your Go module path (same as in `go.mod`)\n\n</aside>\n\n### 2.4 Enable multi-group support (if needed)\n\nMulti-group projects organize APIs into different groups, with each group in its own directory.\nThis is useful when you have APIs for different purposes or domains.\n\n**Check if your project uses multi-group layout** by examining your backup's directory structure:\n\n- **Single-group layout:** All APIs in one group\n  - `api/v1/cronjob_types.go`\n  - `api/v1/job_types.go`\n  - `api/v2/cronjob_types.go`\n\n- **Multi-group layout:** APIs organized by group\n  - `api/batch/v1/cronjob_types.go`\n  - `api/crew/v1/captain_types.go`\n  - `api/sea/v1/ship_types.go`\n\nYou can also check your backup's `PROJECT` file for `multigroup: true`.\n\n**If your project uses multi-group layout**, enable it before creating APIs:\n\n```bash\nkubebuilder edit --multigroup=true\n```\n\n<aside class=\"warning\">\n<h1>Important</h1>\n\nThis must be done before creating any APIs to ensure they're scaffolded in the multi-group structure.\n\n</aside>\n\nWhen following this guide, you'll get the new layout automatically since you're creating a fresh project with the latest version and porting your code into it.\n\n## Step 3: Re-scaffold APIs and Controllers\n\nFor each API resource in your original project, re-scaffold them in the new project.\n\n### 3.1 Identify all your APIs\n\nReview your backup project (`../migration-backup/`) to identify all APIs. **It's recommended to check the backup directory\nregardless of whether you have a `PROJECT` file**, as not all resources may have been created using the CLI.\n\n**Check the directory structure** in your backup to ensure you don't miss any manually created resources:\n\n- Look in the `api/` directory (or `apis/` for projects generated with older Kubebuilder versions) for `*_types.go` files:\n  - Single-group: `api/v1/cronjob_types.go` - extract: version `v1`, kind `CronJob`, group from imports\n  - Multi-group: `api/batch/v1/cronjob_types.go` - extract: group `batch`, version `v1`, kind `CronJob`\n\n- Check for controllers in these locations:\n  - **Current:** `internal/controller/cronjob_controller.go` or `internal/controller/<group>/cronjob_controller.go`\n  - **Legacy:** `controllers/cronjob_controller.go` or `pkg/controllers/cronjob_controller.go`\n\n**If you used the CLI to create all APIs from Kubebuilder `v3.0.0+` you should have them in the `PROJECT` file** under the `resources` section, such as:\n\n```yaml\nresources:\n  - api:\n      crdVersion: v1\n      namespaced: true\n    controller: true\n    group: batch\n    kind: CronJob\n    version: v1\n```\n\n<aside class=\"note\">\n<h1>Tip</h1>\n\nMake a list of all APIs with their group, version, kind, and whether they have a controller.\nThis will help you systematically re-scaffold everything.\n\n</aside>\n\n### 3.2 Create each API and Controller\n\nFor each API identified in step 3.1, re-scaffold it:\n\n```bash\nkubebuilder create api --group batch --version v1 --kind CronJob\n```\n\nWhen prompted:\n- Answer **yes** to \"Create Resource [y/n]\" to generate the API types\n- Answer **yes** to \"Create Controller [y/n]\" if your original project has a controller for this API\n\n**After creating each API**, update the generated manifests and code:\n\n```bash\nmake manifests  # Generate CRD, RBAC, and other config files\nmake generate   # Generate code (e.g., DeepCopy methods)\n```\n\nThen verify everything compiles:\n\n```bash\nmake build\n```\n\nThese steps ensure the newly scaffolded API is properly integrated.\nSee the [Quick Start][quick-start] guide for a detailed walkthrough of the API creation workflow.\n\nRepeat this process for **ALL** APIs in your project.\n\n<aside class=\"note\">\n<h1>Using External Types (controllers for types not defined in your project)</h1>\n\nIf your project has controllers for Kubernetes built-in types (like `Deployment`, `Pod`) or types from other projects:\n\n```bash\nkubebuilder create api --group apps --version v1 --kind Deployment --resource=false --controller=true\n```\n\nOr for CRDs from other projects, i.e. `cert-manager`'s `Certificate` type:\n\n```bash\nkubebuilder create api --group \"cert-manager\" --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io --external-api-module=github.com/cert-manager/cert-manager@v1.18.2\n```\n\nUse `--resource=false` to skip creating the API definition and only scaffold the controller.\n\nEnsure that you check [Using External Types][external-types] for more details.\n\n</aside>\n\nAfter creating all resources, regenerate manifests:\n\n```bash\nmake manifests\nmake generate\n```\n\n### 3.3 Re-scaffold webhooks (if applicable)\n\nIf your original project has webhooks, you need to re-scaffold them.\n\n**Identify webhooks in your backup project:**\n\n1. **From directory structure**, look for webhook files:\n   - Legacy location (v3 and earlier): `api/v1/<kind>_webhook.go` or `api/<group>/<version>/<kind>_webhook.go`\n   - Current location (single-group): `internal/webhook/<version>/<kind>_webhook.go`\n   - Current location (multi-group): `internal/webhook/<group>/<version>/<kind>_webhook.go`\n\n2. **From `PROJECT` file** (if available), check each resource's webhooks section:\n\n```yaml\nresources:\n  - api:\n      ...\n    webhooks:\n      defaulting: true\n      validation: true\n      webhookVersion: v1\n```\n\n**Re-scaffold webhooks:**\n\nFor each resource with webhooks, run:\n\n```bash\nkubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation\n```\n\n**Webhook options:**\n- `--defaulting` - creates a defaulting webhook (sets default values)\n- `--programmatic-validation` - creates a validation webhook (validates create/update/delete operations)\n- `--conversion` - creates a conversion webhook (for multi-version APIs, see next section)\n\n### 3.4 Re-scaffold conversion webhooks (if applicable)\n\nIf your project has multi-version APIs with conversion webhooks, you need to set up the hub-spoke conversion pattern.\n\n<aside class=\"note\">\n<h1>Understanding Hub-Spoke Conversion</h1>\n\nIn Kubernetes multi-version APIs, the **hub** is the version that all other versions (spokes) convert to and from:\n- **Hub version**: Usually the most complete/stable version (often the storage version)\n- **Spoke versions**: All other versions that convert through the hub\n\nThe hub implements `Hub()` marker interface, while spokes implement `ConvertTo()` and `ConvertFrom()` methods to convert to/from the hub.\n\n</aside>\n\n**Setting up conversion webhooks:**\n\nCreate the conversion webhook for the **hub** version, with spoke versions specified using the `--spoke` flag.\n\n**Note:** In the examples below, we use `v1` as the hub for illustration. Choose the version in your project that should be the central conversion point—typically your most feature-complete and stable storage version, not necessarily the oldest or newest.\n\n```bash\nkubebuilder create webhook --group batch --version v1 --kind CronJob --conversion --spoke v2\n```\n\nThis command:\n- Creates conversion webhook for `v1` as the **hub** version\n- Configures `v2` as a **spoke** that converts to/from the hub `v1`\n- Generates `*_conversion.go` files with conversion method stubs\n\n**For multiple spokes**, specify them as a comma-separated list:\n\n```bash\nkubebuilder create webhook --group batch --version v1 --kind CronJob --conversion --spoke v2,v1alpha1\n```\n\nThis sets up `v1` as the **hub** with both `v2` and `v1alpha1` as **spokes**.\n\n**What you need to implement:**\n\nThe command generates method stubs that you'll fill in during Step 4:\n- **Hub version**: Implement `Hub()` method (usually just a marker)\n- **Spoke versions**: Implement `ConvertTo(hub)` and `ConvertFrom(hub)` methods with your conversion logic\n\nSee the [Multi-Version Tutorial][multiversion-tutorial] for comprehensive guidance on implementing the conversion logic.\n\n<aside class=\"note\">\n<H1> Forget a type of webhook ? </h1>\n\nIf you forget a webhook type, use `--force` to re-run the command:\n\n```bash\nkubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --force\n```\n</aside>\n\n<aside class=\"note\">\n<h1>Webhook for External Types</h1>\n\n**For external types**, you can also create webhooks:\n\n```bash\nkubebuilder create webhook --group apps --version v1 --kind Deployment --defaulting --programmatic-validation\n```\n\nMore info: [Webhook Overview][webhook-overview], [Admission Webhook][admission-webhook], and [Creating Webhooks for External Types][external-types-webhooks].\n</aside>\n\nAfter scaffolding all webhooks, verify everything compiles:\n\n```bash\nmake manifests && make build\n```\n\n## Step 4: Port Your Custom Code\n\n<aside class=\"note\">\n\n<h1>Using AI Migration Helper</h1>\n\nIf you used [Step 3: Port Custom Code](./port-code.md) AI migration helper, your code is already ported.\n\nYou may skip to [Step 5](#step-5-test-and-verify). However, it's still recommended to at least review the following steps and do manual validation to ensure all code was properly ported.\n\n</aside>\n\nManually port your custom business logic and configurations from the backup to the new project.\n\n<aside class=\"note\">\n<h1>Use diff tools</h1>\n\nUse IDE diff tools or commands like `diff -r ../migration-backup/ .` to compare directories and identify all customizations you need to port.\nMost modern IDEs support directory-level comparison which makes this process much easier.\n\n</aside>\n\n### 4.1 Port API definitions\n\nCompare and merge your custom API fields and markers from your backup project.\n\n**Files to compare:**\n\n- **Single-group:** `api/v1/<kind>_types.go`\n- **Multi-group:** `api/<group>/<version>/<kind>_types.go`\n\n**What to port:**\n\n1. **Custom fields** in Spec and Status structs\n2. **Validation markers** - e.g., `+kubebuilder:validation:Minimum=0`, `+kubebuilder:validation:Pattern=...`\n3. **CRD generation markers** - e.g., `+kubebuilder:printcolumn`, `+kubebuilder:resource:scope=Cluster`\n4. **SubResources** - e.g., `+kubebuilder:subresource:status`, `+kubebuilder:subresource:scale`\n5. **Documentation comments** - Used for CRD descriptions\n\nSee [CRD Generation][crd-generation], [CRD Validation][crd-validation], and [Markers][markers] for all available markers.\n\n**If your APIs reference a parent package** (e.g., `scheduling.GroupName`), port it:\n\n```bash\nmkdir -p api/<group>/\ncp ../migration-backup/apis/<group>/groupversion_info.go api/<group>/\n```\n\nAfter porting API definitions, regenerate and verify:\n\n```bash\nmake manifests  # Generate CRD manifests from your types\nmake generate   # Generate DeepCopy methods\n```\n\nThis ensures your API types and CRD manifests are properly generated before moving forward.\n\n### 4.2 Port controller logic\n\n**Files to compare:**\n\n- **Current single-group:** `internal/controller/<kind>_controller.go`\n- **Current multi-group:** `internal/controller/<group>/<kind>_controller.go`\n\n**What to port:**\n\n1. **Reconcile function implementation** - Your core business logic\n2. **Helper functions** - Any additional functions in the controller file\n3. **RBAC markers** - `+kubebuilder:rbac:groups=...,resources=...,verbs=...`\n4. **Additional watches** - Custom watch configurations in `SetupWithManager`\n5. **Imports** - Any additional packages your controller needs\n6. **Struct fields** - Custom fields added to the Reconciler struct\n\nSee [RBAC Markers][rbac-markers] for details on permission markers.\n\nAfter porting controller logic, regenerate manifests and verify compilation:\n\n```bash\nmake generate\nmake manifests\nmake build\n```\n\n### 4.3 Port webhook implementations\n\nWebhooks have changed location between Kubebuilder versions. Be aware of the path differences:\n\n**Legacy webhook location** (Kubebuilder v3 and earlier):\n- `api/v1/<kind>_webhook.go`\n- `api/<group>/<version>/<kind>_webhook.go`\n\n**Current webhook location:**\n- Single-group: `internal/webhook/<version>/<kind>_webhook.go`\n- Multi-group: `internal/webhook/<group>/<version>/<kind>_webhook.go`\n\n**What to port:**\n\n1. **Defaulting webhook** - `Default()` method implementation\n2. **Validation webhook** - `ValidateCreate()`, `ValidateUpdate()`, `ValidateDelete()` methods\n3. **Conversion webhook** - `ConvertTo()` and `ConvertFrom()` methods (for multi-version APIs)\n4. **Helper functions** - Any validation or defaulting helper functions\n5. **Webhook markers** - Usually auto-generated, but verify they match your needs\n\n<aside class=\"note\">\n<h1>Important</h1>\n\nEven if the webhook file is in a different location, the implementation logic remains largely the same.\nCopy the method implementations, not just the entire file.\n\n</aside>\n\nSee [Webhook Overview][webhook-overview], [Admission Webhook][admission-webhook], and the [Multi-Version Tutorial][multiversion-tutorial] for details.\n\n**For conversion webhooks:**\n\nIf you have conversion webhooks, ensure you used the `create webhook --conversion --spoke <version>` command in Step 3.4. This sets up the hub-spoke infrastructure automatically. You only need to fill in the conversion logic in the `ConvertTo()` and `ConvertFrom()` methods in your spoke versions, and the `Hub()` method in your hub version.\n\nThe command creates all the necessary boilerplate - you just implement the business logic for converting fields between versions.\n\nAfter porting webhooks, regenerate and verify:\n\n```bash\nmake generate\nmake manifests\nmake build\n```\n\n### 4.4 Port main.go customizations (if any)\n\n**File:** `cmd/main.go`\n\nMost projects don't need to customize `main.go` as Kubebuilder handles all the standard setup automatically\n(registering APIs, setting up controllers and webhooks, manager initialization, metrics, etc.).\n\nOnly port customizations that are not part of the standard scaffold. Compare your backup `main.go` with the\nnew scaffolded one to identify any custom logic you added.\n\n### 4.5 Configure Kustomize manifests\n\nThe `config/` directory contains Kustomize manifests for deploying your operator. Compare with your backup to ensure all configurations are properly set up.\n\n**Review and update these directories:**\n\n1. **`config/default/kustomization.yaml`** - Main kustomization file\n   - Ensure webhook configurations are enabled if you have webhooks (uncomment webhook-related patches)\n   - Ensure cert-manager is enabled if using webhooks (uncomment certmanager resources)\n   - Enable or disable metrics endpoint based on your original configuration\n   - Review namespace and name prefix settings\n\n2. **`config/manager/`** - Controller manager deployment\n   - Usually no changes are needed unless you have customizations. In that case, compare resource limits and requests with your backup and check environment variables\n\n3. **`config/rbac/`** - RBAC configurations\n   - Usually auto-generated from markers - no manual changes needed\n   - Only check if you have custom role bindings or service account configurations not covered by markers\n\n4. **`config/webhook/`** - Webhook configurations (if applicable)\n   - Usually auto-generated - no manual changes needed\n   - Only check if you have custom webhook service or certificate configurations\n\n5. **`config/samples/`** - Sample CR manifests\n   - Copy your sample resources from the backup\n\nAfter configuring Kustomize, verify the manifests build correctly:\n\n```bash\nmake all\nmake build-installer\n```\n\n### 4.6 Port additional customizations\n\nPort any additional packages, dependencies, and customizations from your backup:\n\n**Additional packages** (e.g., `pkg/util`):\n\n```bash\ncp -r ../migration-backup/pkg/<package-name> pkg/\n# Update import paths (works on both macOS and Linux)\nfind pkg/ -name \"*.go\" -exec sed -i.bak 's|<module>/apis/|<module>/api/|g' {} \\;\nfind pkg/ -name \"*.go.bak\" -delete\n```\n\nFor dependencies, run `go mod tidy` or copy `go.mod`/`go.sum` from backup for complex projects.\n\nCheck for additional customizations (Makefile, Dockerfile, test files). Use diff tools to compare with backup and identify missed files.\n\n\n<aside class=\"note\">\n<h1>Using diff tools</h1>\n\nUse your IDE's diff tools to compare the current directory with your backup (`../migration-backup/`) or use git to compare\nyour current branch with your main branch. This helps identify any files you may have missed.\n\n</aside>\n\nAfter porting all customizations, verify everything builds:\n\n```bash\nmake all\n```\n\n## Step 5: Test and Verify\n\nCompare against the backup to ensure all customizations were correctly ported, such as:\n\n```bash\ndiff -r --brief ../migration-backup/ . | grep \"Only in ../migration-backup\"\n```\n\nRun tests and verify functionality:\n\n```bash\nmake test && make lint-fix\n```\n\nDeploy to a test cluster (e.g. [kind][kind-doc]) and verify the changes (i.e. validate expected behavior, run regression checks, confirm the full CI pipeline still passes, and execute the e2e tests).\n\n<aside class=\"note\">\n\n<h1>If You Have a Helm Chart</h1>\n\nIf you had a Helm chart to distribute your project, you may want to regenerate it with the [helm/v2-alpha plugin](../plugins/available/helm-v2-alpha.md), then apply your customizations.\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\nCompare your backup's `chart/values.yaml` and custom templates with the newly generated chart, and apply your customizations and ensure that all is still working\nas before.\n\n</aside>\n\n## Additional Resources\n\n- [Migration Overview](../migrations.md) - Overview of all migration options\n- [PROJECT File Reference][project-config] - Understanding the PROJECT file\n- [What's in a basic project?][basic-project-doc] - Understanding project structure\n- [Alpha Generate Command](../reference/commands/alpha_generate.md) - Automated re-scaffolding\n- [Alpha Update Command](../reference/commands/alpha_update.md) - Automated migration\n- [Using External Types][external-types] - Controllers for types not defined in your project\n- [CRD Generation][crd-generation] - Generating CRDs from Go types\n- [CRD Validation][crd-validation] - Adding validation to your APIs\n- [Markers][markers] - All available markers for code generation\n- [RBAC Markers][rbac-markers] - Generating RBAC manifests\n- [Webhook Overview][webhook-overview] - Understanding webhooks\n- [Admission Webhook][admission-webhook] - Implementing admission webhooks\n- [Multi-Version Tutorial][multiversion-tutorial] - Handling multiple API versions\n- [Deploying cert-manager][cert-manager] - Required for webhooks\n- [Configuring EnvTest][envtest] - Testing with EnvTest\n\n[quick-start]: ../quick-start.md\n[project-config]: ../reference/project-config.md\n[basic-project-doc]: ../cronjob-tutorial/basic-project.md\n[external-types]: ../reference/using_an_external_resource.md\n[external-types-webhooks]: ../reference/using_an_external_resource.md#creating-a-webhook-to-manage-an-external-type\n[crd-generation]: ../reference/generating-crd.md\n[crd-validation]: ../reference/markers/crd-validation.md\n[markers]: ../reference/markers.md\n[rbac-markers]: ../reference/markers/rbac.md\n[webhook-overview]: ../reference/webhook-overview.md\n[admission-webhook]: ../reference/admission-webhook.md\n[multiversion-tutorial]: ../multiversion-tutorial/tutorial.md\n[cert-manager]: ../cronjob-tutorial/cert-manager.md\n[envtest]: ../reference/envtest.md\n[standard-go-project]: https://github.com/golang-standards/project-layout\n[kind-doc]: ../reference/kind.md\n[autoupdate-plugin]: ../plugins/available/autoupdate-v1-alpha.md\n[alpha-update]: ../reference/commands/alpha_update.md"
  },
  {
    "path": "docs/book/src/migration/multi-group.md",
    "content": "# Single Group to Multi-Group\n\nKubebuilder scaffolds single-group projects by default to keep things simple, as most projects don't require multiple API groups. However, you can convert an existing single-group project to use multi-group layout when needed. This reorganizes your APIs and controllers into group-specific directories.\n\nSee the [design doc][multigroup-design] for the rationale behind this design decision.\n\n<aside class=\"note\">\n\n<h1>What's a Multi-Group Project?</h1>\n\nMulti-group layout is useful when you're building APIs for different purposes or domains. For example, you might have:\n- A `batch` group for job-related resources (CronJob, Job)\n- An `apps` group for application resources (Deployment, StatefulSet)\n- A `crew` group for team management resources (Captain, Sailor)\n\nEach group gets its own directory, keeping things organized as your project grows.\n\nSee [Groups and Versions and Kinds, oh my!][gvks] to better understand API groups.\n\n</aside>\n\n<aside class=\"note\">\n\n<h1>AI-Assisted Migration</h1>\n\nThis migration involves repetitive file moving and import path updates. If you're using an AI coding assistant, see the [AI-Assisted Migration](#ai-assisted-migration) section for ready-to-use instructions.\n\n</aside>\n\n## Understanding the Layouts\n\nHere's what changes when you go from single-group to multi-group:\n\n**Single-group layout (default):**\n```\napi/<version>/*_types.go                  All your CRD schemas in one place\ninternal/controller/*                     All your controllers together\ninternal/webhook/<version>/*              Webhooks organized by version (if you have any)\n```\n\n**Multi-group layout:**\n```\napi/<group>/<version>/*_types.go          CRD schemas organized by group\ninternal/controller/<group>/*             Controllers organized by group\ninternal/webhook/<group>/<version>/*      Webhooks organized by group and version (if you have any)\n```\n\nYou can tell which layout you're using by checking your `PROJECT` file for `multigroup: true`.\n\n## Migration Steps\n\nThe following steps migrate the [CronJob example][cronjob-tutorial] from single-group to multi-group layout.\n\n<aside class=\"note\">\n<h1>Starting new projects with multigroup</h1>\n\nIf you're starting a **new project** and already know you want multigroup layout, you can use the `--multigroup` flag during initialization:\n\n```bash\nkubebuilder init --domain example.org --multigroup\n```\n\nThis guide is for **existing projects** that need to be migrated from single-group to multi-group layout.\n\n</aside>\n\n### Step 1: Enable multi-group mode\n\nFirst, tell Kubebuilder you want to use multi-group layout:\n\n```bash\nkubebuilder edit --multigroup=true\n```\n\nThis command updates your `PROJECT` file by adding `multigroup: true`. After this change:\n- **New APIs** you create will automatically use the multi-group structure (`api/<group>/<version>/`)\n- **Existing APIs** remain in their current location and must be migrated manually (steps 3-9 below)\n\n<aside class=\"note\">\n<h1>What this command changes</h1>\n\nThe command adds or updates this line in your PROJECT file:\n\n```yaml\nmultigroup: true\n```\n\nThis setting tells Kubebuilder to use group-based directories for all future scaffolding operations.\n\n</aside>\n\n### Step 2: Identify your group name\n\nCheck `api/v1/groupversion_info.go` to find your group name:\n\n```go\n// +groupName=batch.tutorial.kubebuilder.io\npackage v1\n```\n\nThe group name is the first part before the dot (`batch` in this example).\n\n### Step 3: Move your APIs\n\nCreate a directory for your group and move your version directories:\n\n```bash\nmkdir -p api/batch\nmv api/v1 api/batch/\n```\n\nIf you have multiple versions (like `v1`, `v2`, etc.), move them all:\n\n```bash\nmv api/v2 api/batch/\n```\n\n### Step 4: Move your controllers\n\nCreate a group directory and move all controller files:\n\n```bash\nmkdir -p internal/controller/batch\nmv internal/controller/*.go internal/controller/batch/\n```\n\nThis will move all your controller files, including `suite_test.go`, into the group directory. Each group needs its own test suite.\n\n### Step 5: Move your webhooks (if you have any)\n\nIf your project has webhooks (check for an `internal/webhook/` directory), add the group directory:\n\n```bash\nmkdir -p internal/webhook/batch\nmv internal/webhook/v1 internal/webhook/batch/\nmv internal/webhook/v2 internal/webhook/batch/  # if v2 exists\n```\n\nIf you don't have webhooks, skip this step.\n\n### Step 6: Update import paths\n\nUpdate all import statements to point to the new locations.\n\n**What used to look like this:**\n```go\nimport (\n    batchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n    \"tutorial.kubebuilder.io/project/internal/controller\"\n)\n```\n\n**Should now look like this:**\n```go\nimport (\n    batchv1 \"tutorial.kubebuilder.io/project/api/batch/v1\"\n    batchcontroller \"tutorial.kubebuilder.io/project/internal/controller/batch\"\n)\n```\n\n**If you have webhooks, you'll also need to update those imports:**\n```go\n// Before\nwebhookv1 \"tutorial.kubebuilder.io/project/internal/webhook/v1\"\n\n// After\nwebhookbatchv1 \"tutorial.kubebuilder.io/project/internal/webhook/batch/v1\"\n```\n\nFiles to check and update:\n- `cmd/main.go`\n- `internal/controller/batch/*.go`\n- `internal/webhook/batch/v1/*.go` (if you have webhooks)\n- `api/batch/v1/*_test.go`\n\nTip: Use your IDE's \"Find and Replace\" feature across the project.\n\n### Step 7: Update the PROJECT file\n\nThe `kubebuilder edit --multigroup=true` command sets `multigroup: true` in your PROJECT file but doesn't update paths for existing APIs. You need to manually update the `path` field for each resource.\n\n**Verify your PROJECT file has these changes:**\n\n1. **Check that `multigroup: true` is set** (at the top level):\n\n```yaml\nlayout:\n- go.kubebuilder.io/v4\nmultigroup: true  # Must be true\nprojectName: project\n```\n\n2. **Update the `path` field for each resource**:\n\n**Before:**\n```yaml\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  group: batch\n  kind: CronJob\n  path: tutorial.kubebuilder.io/project/api/v1  # Old path\n  version: v1\n```\n\n**After:**\n```yaml\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  group: batch\n  kind: CronJob\n  path: tutorial.kubebuilder.io/project/api/batch/v1  # New path with group\n  version: v1\n```\n\nRepeat this for **all resources** in your PROJECT file.\n\n### Step 8: Update test suite CRD paths\n\nUpdate the CRD directory path in test suites. Since files moved one level deeper, add one more `\"..\"` to the path.\n\n**In `internal/controller/batch/suite_test.go`:**\n\n**Before (was at `internal/controller/suite_test.go`):**\n```go\ntestEnv = &envtest.Environment{\n    CRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n}\n```\n\n**After (now at `internal/controller/batch/suite_test.go`):**\n```go\ntestEnv = &envtest.Environment{\n    CRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n}\n```\n\n**If you have webhooks, update `internal/webhook/batch/v1/webhook_suite_test.go`:**\n\n**Before (was at `internal/webhook/v1/webhook_suite_test.go`):**\n```go\ntestEnv = &envtest.Environment{\n    CRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n}\n```\n\n**After (now at `internal/webhook/batch/v1/webhook_suite_test.go`):**\n```go\ntestEnv = &envtest.Environment{\n    CRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n}\n```\n\n### Step 9: Verify the migration\n\nRun the following commands to verify everything works:\n\n```bash\nmake manifests      # Regenerate CRDs and RBAC\nmake generate       # Regenerate code\nmake test           # Run tests\nmake build          # Build the project\n```\n\n## AI-Assisted Migration\n\nIf you're using an AI coding assistant (Cursor, GitHub Copilot, etc.), you can automate most of the migration steps.\n\n<aside class=\"note\">\n\n<h1>AI Migration Instructions</h1>\n\n**Prerequisites:**\n1. First, identify the API group name from `api/v1/groupversion_info.go` (look for `+groupName=<group>.<domain>`)\n2. Get your module path from `go.mod` (first line: `module <repo>`)\n\n**Instructions to provide to your AI assistant:**\n\nGive your AI assistant these instructions, replacing the values in the first two lines:\n\n```\nI need to migrate this Kubebuilder project to multi-group layout.\n\nProject details:\n- Group name: batch\n- Module path: tutorial.kubebuilder.io/project\n\nContext:\nKubebuilder projects have three main code locations:\n- api/<version>/ - Contains CRD type definitions (*_types.go files)\n- internal/controller/ - Contains reconcilers (*_controller.go files)\n- internal/webhook/<version>/ - Contains webhooks (*_webhook.go files) [if present]\n\nMulti-group layout reorganizes these into group-specific directories:\n- api/<group>/<version>/ - Types organized by API group\n- internal/controller/<group>/ - Controllers organized by group\n- internal/webhook/<group>/<version>/ - Webhooks organized by group\n\nThis keeps code organized as projects grow to support multiple API groups.\n\nReferences:\n- Kubebuilder Book: https://book.kubebuilder.io\n\nSteps to execute:\n\n1. Enable multi-group mode:\n   Run: kubebuilder edit --multigroup=true\n\n2. Move API files:\n   mkdir -p api/batch\n   mv api/v1 api/batch/\n   mv api/v2 api/batch/  # if v2 exists\n\n3. Move controller files:\n   mkdir -p internal/controller/batch\n   mv internal/controller/*.go internal/controller/batch/\n\n4. Move webhook version directories (ONLY if internal/webhook/ exists):\n   # Skip this step entirely if you don't have an internal/webhook/ directory\n   if [ -d \"internal/webhook\" ]; then\n     mkdir -p internal/webhook/batch\n     mv internal/webhook/v1 internal/webhook/batch/ 2>/dev/null || true\n     mv internal/webhook/v2 internal/webhook/batch/ 2>/dev/null || true\n   fi\n\n5. Update all import paths:\n   - In cmd/main.go, internal/controller/batch/*.go, api/batch/*/*.go (and webhook files if they exist)\n   - Replace: tutorial.kubebuilder.io/project/api/v1 -> tutorial.kubebuilder.io/project/api/batch/v1\n   - Replace: tutorial.kubebuilder.io/project/api/v2 -> tutorial.kubebuilder.io/project/api/batch/v2\n   - Replace: tutorial.kubebuilder.io/project/internal/controller -> tutorial.kubebuilder.io/project/internal/controller/batch\n   - If you have webhooks, also replace:\n     tutorial.kubebuilder.io/project/internal/webhook/v1 -> tutorial.kubebuilder.io/project/internal/webhook/batch/v1\n     tutorial.kubebuilder.io/project/internal/webhook/v2 -> tutorial.kubebuilder.io/project/internal/webhook/batch/v2\n\n6. Update PROJECT file:\n   - Verify multigroup: true is set (should be set by step 1)\n   - For each resource entry, update the path field\n   - From: tutorial.kubebuilder.io/project/api/v1\n   - To: tutorial.kubebuilder.io/project/api/batch/v1\n   - Example:\n     ```yaml\n     layout:\n     - go.kubebuilder.io/v4\n     multigroup: true  # This must be true\n     resources:\n     - api:\n         crdVersion: v1\n         namespaced: true\n       controller: true\n       domain: tutorial.kubebuilder.io\n       group: batch\n       kind: CronJob\n       path: tutorial.kubebuilder.io/project/api/batch/v1  # Updated path\n       version: v1\n     ```\n\n7. Fix test suite CRD paths (add one more \"..\"):\n   - In internal/controller/batch/suite_test.go:\n     From: filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")\n     To: filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")\n   - If you have webhooks, also in internal/webhook/batch/v1/webhook_suite_test.go:\n     From: filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")\n     To: filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")\n\n8. Verify:\n   Run: make manifests && make generate && make test\n```\n\n**After AI completes:**\n- Review the changes carefully\n- Verify import paths are correct\n- Check PROJECT file paths\n- Run `make test` to catch any issues\n\n</aside>\n\n[gvks]: /cronjob-tutorial/gvks.md \"Groups and Versions and Kinds, oh my!\"\n[cronjob-tutorial]: /cronjob-tutorial/cronjob-tutorial.md \"Tutorial: Building CronJob\"\n[multigroup-design]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/simplified-scaffolding.md\n"
  },
  {
    "path": "docs/book/src/migration/namespace-scoped.md",
    "content": "# Migrating to Namespace-Scoped Manager\n\nThis guide covers converting **existing cluster-scoped projects** to namespace-scoped deployment.\n\n<aside class=\"note\">\n<h1>Creating New Namespace-Scoped Projects</h1>\n\nIf you're creating a **new project**, simply use:\n\n```bash\nkubebuilder init --domain example.com --namespaced\n```\n\nAll files including `cmd/main.go` and RBAC configurations will be scaffolded correctly. All controllers created with `kubebuilder create api` will automatically have the `namespace=` parameter in their RBAC markers. No manual changes or migration steps are needed.\n</aside>\n\nBy default, Kubebuilder scaffolds cluster-scoped managers that watch and manage resources across all namespaces. This guide shows how to convert an existing cluster-scoped project to namespace-scoped deployment, limiting the manager to watch only specific namespace(s).\n\n## When to Use Namespace-Scoped\n\n**Use namespace-scoped when:**\n- Building tenant-specific managers in multi-tenant clusters\n- Security policies require least-privilege (no cluster-wide permissions)\n- Need multiple manager instances in different namespaces\n- Managing only namespace-scoped resources (Deployments, Services, ConfigMaps, etc.)\n\n**Use cluster-scoped (default) when:**\n- Managing cluster-scoped resources (Nodes, ClusterRoles, Namespaces, etc.)\n- Single manager instance managing resources across all namespaces\n\n<aside class=\"note\">\n<h1>AI-Assisted Migration</h1>\n\nThis migration involves updating RBAC markers across multiple controller files. If you're using an AI coding assistant, see the [AI-Assisted Migration](#ai-assisted-migration) section for ready-to-use instructions.\n\n</aside>\n\n## Migration Steps\n\n**Quick Summary:**\n1. Run `kubebuilder edit --namespaced --force` - scaffolds Role/RoleBinding and updates manager.yaml\n2. Update cmd/main.go to configure namespace-scoped cache\n3. Add `namespace=` parameter to RBAC markers in existing controller files\n4. Run `make manifests` - regenerate RBAC from updated markers\n5. Verify and deploy\n\n<aside class=\"warning\">\n<h2>Manual Steps Required</h2>\n\nThe `edit` command scaffolds RBAC files and updates manager.yaml automatically (with `--force`), but **cannot** update existing controller files or cmd/main.go.\n\nYou must manually update:\n- cmd/main.go namespace-scoped cache configuration\n- Existing controller RBAC markers\n\n**Note:** New controllers created after enabling namespaced mode will have correct RBAC markers automatically.\n</aside>\n\n## Detailed Steps:\n\n### 1. Enable namespace-scoped mode\n\n```bash\nkubebuilder edit --namespaced --force\n```\n\nThis command automatically:\n- Sets `namespaced: true` in your PROJECT file\n- Scaffolds `config/rbac/role.yaml` with `kind: Role` (namespace-scoped)\n- Scaffolds `config/rbac/role_binding.yaml` with `kind: RoleBinding`\n- Regenerates `config/manager/manager.yaml` with WATCH_NAMESPACE environment variable\n- Regenerates admin/editor/viewer roles with `kind: Role` (namespace-scoped) for all existing APIs\n\n**Note:** The `--force` flag regenerates config/manager/manager.yaml. Without `--force`, you must manually add WATCH_NAMESPACE (see below).\n\n### 2. Update cmd/main.go (Required Manual Step)\n\nThe edit command cannot update cmd/main.go automatically. You must manually add namespace-scoped configuration.\n\n**a. Add import:**\n```go\nimport (\n    // ... existing imports ...\n    \"sigs.k8s.io/controller-runtime/pkg/cache\"\n)\n```\n\n**b. Add helper functions (after `init()` and before `main()`):**\n```go\n// getWatchNamespace returns the namespace(s) the manager should watch for changes.\n// It reads the value from the WATCH_NAMESPACE environment variable.\nfunc getWatchNamespace() (string, error) {\n    watchNamespaceEnvVar := \"WATCH_NAMESPACE\"\n    ns, found := os.LookupEnv(watchNamespaceEnvVar)\n    if !found {\n        return \"\", fmt.Errorf(\"%s must be set\", watchNamespaceEnvVar)\n    }\n    return ns, nil\n}\n\n// setupCacheNamespaces configures the cache to watch specific namespace(s).\nfunc setupCacheNamespaces(namespaces string) cache.Options {\n    defaultNamespaces := make(map[string]cache.Config)\n    for _, ns := range strings.Split(namespaces, \",\") {\n        defaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}\n    }\n    return cache.Options{\n        DefaultNamespaces: defaultNamespaces,\n    }\n}\n```\n\n**c. In `main()` function, before `ctrl.NewManager()`, add:**\n```go\n// Get the namespace(s) for namespace-scoped mode from WATCH_NAMESPACE environment variable.\nwatchNamespace, err := getWatchNamespace()\nif err != nil {\n    setupLog.Error(err, \"Unable to get WATCH_NAMESPACE\")\n    os.Exit(1)\n}\n```\n\n**d. Update manager creation to use namespace-scoped cache:**\n```go\nmgrOptions := ctrl.Options{\n    Scheme:                 scheme,\n    Metrics:                metricsServerOptions,\n    WebhookServer:          webhookServer,\n    HealthProbeBindAddress: probeAddr,\n    LeaderElection:         enableLeaderElection,\n    LeaderElectionID:       \"your-leader-election-id\",\n    // ... other existing options ...\n}\n\n// Configure cache to watch namespace(s) specified in WATCH_NAMESPACE\nmgrOptions.Cache = setupCacheNamespaces(watchNamespace)\nsetupLog.Info(\"Watching namespace(s)\", \"namespaces\", watchNamespace)\n\nmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)\nif err != nil {\n    setupLog.Error(err, \"Failed to start manager\")\n    os.Exit(1)\n}\n```\n\n<aside class=\"note\">\n<h1>If You Didn't Use --force</h1>\n\nIf you ran `kubebuilder edit --namespaced` without `--force`, manually add WATCH_NAMESPACE to `config/manager/manager.yaml`:\n\n```yaml\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        env:\n        - name: WATCH_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n```\n\nWith `--force`, this is done automatically. Skip if you used `--force`.\n</aside>\n\n### 3. Update RBAC markers in existing controllers\n\nFor each **existing controller file**, add the `namespace=` parameter to RBAC markers.\n\n**Find controller files:**\n- Look for files containing `func (r *SomeReconciler) Reconcile(`\n- Common locations: `internal/controller/*_controller.go`\n\nIn `internal/controller/cronjob_controller.go`:\n\n**Before (cluster-scoped):**\n```go\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n```\n\n**After (namespace-scoped):**\n```go\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,namespace=<project-name>-system,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,namespace=<project-name>-system,resources=cronjobs/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,namespace=<project-name>-system,resources=cronjobs/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n```\n\nReplace `project-system` with your namespace (found in `config/default/kustomization.yaml` under the `namespace:` field).\n\n<aside class=\"note\">\n<h1>New Controllers Get This Automatically</h1>\n\nAfter running `kubebuilder edit --namespaced --force`, any new controllers created will automatically have the `namespace=` parameter:\n\n```bash\nkubebuilder create api --group myapp --version v1 --kind MyNewKind --controller=true --resource=true\n```\n\nGenerated controller will include:\n```go\n// +kubebuilder:rbac:groups=myapp.example.com,namespace=<project-name>-system,resources=mynewkinds,verbs=...\n```\n\nOnly existing controllers need manual updates!\n</aside>\n\n### 4. Regenerate RBAC manifests\n\nAfter updating RBAC markers in Step 3, regenerate the RBAC manifests:\n\n```bash\nmake manifests      # Regenerate RBAC from updated controller markers\n```\n\nVerify the generated files show `kind: Role` instead of `kind: ClusterRole`:\n\n**config/rbac/role.yaml:**\n```yaml\nkind: Role\nmetadata:\n  name: manager-role\n  # Note: namespace is added by kustomize during build, not in source\n```\n\n**config/rbac/*_editor_role.yaml, *_viewer_role.yaml, *_admin_role.yaml:**\n```yaml\nkind: Role\nmetadata:\n  name: cronjob-editor-role\n  # Note: namespace is added by kustomize during build, not in source\n```\n\n<aside class=\"note\">\n<h1>Metrics Auth Role Stays Cluster-Scoped</h1>\n\nThe `config/rbac/metrics_auth_role.yaml` will remain `kind: ClusterRole` - this is correct. The metrics authentication uses cluster-scoped APIs (TokenReview, SubjectAccessReview) and must stay cluster-scoped even in namespace-scoped projects.\n\n</aside>\n\n### 5. Verify and deploy\n\nRun tests to verify everything works:\n\n```bash\nmake generate       # Regenerate code\nmake test           # Run tests\n```\n\nDeploy and verify:\n\n```bash\nmake deploy IMG=<your-image>\n\n# Verify RBAC is namespace-scoped (not cluster-scoped)\nkubectl get role,rolebinding -n <manager-namespace>\n\n# Test: Create a resource in the manager's namespace - should be reconciled\nkubectl apply -f config/samples/ -n <manager-namespace>\n\n# Test: Create a resource in a different namespace - should NOT be reconciled\nkubectl apply -f config/samples/ -n other-namespace\n```\n\n<aside class=\"warning\">\n\n<h1>Webhooks and Namespace-Scoped Mode</h1>\n\nIf your project has webhooks, the manager cache is restricted to `WATCH_NAMESPACE`, but webhooks receive requests from all namespaces by default.\n\n**The Problem:**\n\nYour webhook server receives admission requests from all namespaces, but the cache only has data from `WATCH_NAMESPACE`. If a webhook handler queries the cache for an object outside the watched namespaces, the lookup fails.\n\n**Solution:**\n\nConfigure `namespaceSelector` or `objectSelector` on your webhooks to align webhook scope with the cache. Currently, controller-gen does not have markers for this. You must add these manually using Kustomize patches.\n\nSee the [Webhook Bootstrap Problem](../reference/webhook-bootstrap-problem.html) guide for detailed steps on creating and applying namespace selector patches.\n\n</aside>\n\n## AI-Assisted Migration\n\nIf you're using an AI coding assistant (Cursor, GitHub Copilot, etc.), you can automate the manual migration steps.\n\n<aside class=\"note\">\n\n<h1>AI Migration Instructions</h1>\n\n**Instructions to provide to your AI assistant:**\n\n```\nI need to migrate this Kubebuilder project from cluster-scoped to namespace-scoped.\n\nFirst, get the namespace value:\n- Read config/default/kustomization.yaml and find the \"namespace:\" field\n- Use that value for all namespace= parameters in RBAC markers\n\nContext:\nBy default, Kubebuilder projects are cluster-scoped. Namespace-scoped projects watch only\nspecific namespace(s) via the WATCH_NAMESPACE environment variable.\n\nReferences:\n- Kubebuilder Book: https://book.kubebuilder.io/reference/manager-scope.html\n\nSteps to execute:\n\n1. Enable namespace-scoped mode:\n   Run: kubebuilder edit --namespaced\n\n   This automatically:\n   - Updates PROJECT file with namespaced: true\n   - Scaffolds Role/RoleBinding (instead of ClusterRole/ClusterRoleBinding)\n   - Regenerates admin/editor/viewer roles with kind: Role\n\n2. Add WATCH_NAMESPACE to config/manager/manager.yaml:\n\n   Find the manager container under spec.template.spec.containers (name: manager)\n   and add the env section:\n\n   spec:\n     template:\n       spec:\n         containers:\n         - name: manager\n           env:\n           - name: WATCH_NAMESPACE\n             valueFrom:\n               fieldRef:\n                 fieldPath: metadata.namespace\n\n3. Update cmd/main.go:\n\n   a. Add import:\n\n   import (\n       // ... existing imports ...\n       \"sigs.k8s.io/controller-runtime/pkg/cache\"\n   )\n\n   b. Add these two helper functions after init() and before main():\n\n   // getWatchNamespace returns the namespace(s) the manager should watch for changes.\n   // It reads the value from the WATCH_NAMESPACE environment variable.\n   func getWatchNamespace() (string, error) {\n       watchNamespaceEnvVar := \"WATCH_NAMESPACE\"\n       ns, found := os.LookupEnv(watchNamespaceEnvVar)\n       if !found {\n           return \"\", fmt.Errorf(\"%s must be set\", watchNamespaceEnvVar)\n       }\n       return ns, nil\n   }\n\n   // setupCacheNamespaces configures the cache to watch specific namespace(s).\n   func setupCacheNamespaces(namespaces string) cache.Options {\n       defaultNamespaces := make(map[string]cache.Config)\n       for _, ns := range strings.Split(namespaces, \",\") {\n           defaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}\n       }\n       return cache.Options{\n           DefaultNamespaces: defaultNamespaces,\n       }\n   }\n\n   c. In main() function, find ctrl.SetLogger() and add right after it:\n\n   // Get the namespace(s) for namespace-scoped mode from WATCH_NAMESPACE environment variable.\n   watchNamespace, err := getWatchNamespace()\n   if err != nil {\n       setupLog.Error(err, \"Unable to get WATCH_NAMESPACE\")\n       os.Exit(1)\n   }\n\n   d. Find the ctrl.NewManager() call and replace it with:\n\n   mgrOptions := ctrl.Options{\n       Scheme:                 scheme,\n       Metrics:                metricsServerOptions,\n       WebhookServer:          webhookServer,\n       HealthProbeBindAddress: probeAddr,\n       LeaderElection:         enableLeaderElection,\n       LeaderElectionID:       \"your-leader-election-id\",\n       // ... keep all other existing options from the original ctrl.NewManager call ...\n   }\n\n   // Configure cache to watch namespace(s) specified in WATCH_NAMESPACE\n   mgrOptions.Cache = setupCacheNamespaces(watchNamespace)\n   setupLog.Info(\"Watching namespace(s)\", \"namespaces\", watchNamespace)\n\n   mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)\n   if err != nil {\n       setupLog.Error(err, \"Failed to start manager\")\n       os.Exit(1)\n   }\n\n5. Update RBAC markers in existing controller files:\n\n   Important: Only update RBAC markers in controller files (files containing \"Reconcile\" function).\n   Do not modify webhook files (files in internal/webhook/ or api/*/webhook.go).\n\n   How to find controller files in this project:\n   - Search for all Go files containing \"func (r *\" and \"Reconcile(\"\n   - Common locations: internal/controller/, internal/controller/*/, controllers/\n   - File pattern: *_controller.go (but verify by checking for Reconcile function)\n\n   For EACH controller file found:\n   - Locate ALL +kubebuilder:rbac markers in that file\n   - Add namespace=<value-from-kustomization> parameter to each marker\n\n   Example transformation:\n\n   Before:\n   // +kubebuilder:rbac:groups=myapp.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n   // +kubebuilder:rbac:groups=myapp.example.com,resources=mykinds/status,verbs=get;update;patch\n   // +kubebuilder:rbac:groups=myapp.example.com,resources=mykinds/finalizers,verbs=update\n   // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch\n   // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n\n   After:\n   // +kubebuilder:rbac:groups=myapp.example.com,namespace=<value-from-kustomization>,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n   // +kubebuilder:rbac:groups=myapp.example.com,namespace=<value-from-kustomization>,resources=mykinds/status,verbs=get;update;patch\n   // +kubebuilder:rbac:groups=myapp.example.com,namespace=<value-from-kustomization>,resources=mykinds/finalizers,verbs=update\n   // +kubebuilder:rbac:groups=core,namespace=<value-from-kustomization>,resources=events,verbs=create;patch\n   // +kubebuilder:rbac:groups=apps,namespace=<value-from-kustomization>,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n\n   Important rules:\n   - Add namespace= after the groups= parameter\n   - Use the namespace value from config/default/kustomization.yaml\n   - Update all +kubebuilder:rbac markers in each controller file\n   - Do not modify webhook files - webhooks use certificate-based auth, not RBAC\n   - Do not add namespace= to metrics-auth-role markers (those stay cluster-scoped)\n\n6. Regenerate RBAC manifests:\n   Run: make manifests\n\n   This regenerates config/rbac/role.yaml from the updated controller markers.\n   Verify it shows kind: Role (not ClusterRole).\n\n7. Verify the migration:\n   Run: make generate\n\n   Verify files were updated correctly:\n   - config/rbac/role.yaml - should be kind: Role\n   - config/manager/manager.yaml - should have WATCH_NAMESPACE env var\n   - cmd/main.go - should have getWatchNamespace() and setupCacheNamespaces() functions\n   - All controller files - should have namespace= in RBAC markers\n\nDone! After this migration:\n- The project is now namespace-scoped\n- Existing controllers have been updated with namespace= RBAC markers\n- Future controllers created with `kubebuilder create api` will automatically include\n  namespace= in their RBAC markers - no manual updates needed!\n\n```\n\n</aside>\n\n\n## Multi-Namespace Support\n\nThe `WATCH_NAMESPACE` environment variable supports comma-separated values to watch multiple specific namespaces:\n\n```yaml\nenv:\n- name: WATCH_NAMESPACE\n  value: \"namespace-1,namespace-2,namespace-3\"\n```\n\nNote: You'll need to create Role/RoleBinding in each namespace for proper RBAC.\n\n## Reverting to Cluster-Scoped\n\nTo revert back to cluster-scoped:\n\n```bash\nkubebuilder edit --namespaced=false --force\n```\n\nThis command automatically:\n- Sets `namespaced: false` in your PROJECT file\n- Scaffolds `config/rbac/role.yaml` with `kind: ClusterRole`\n- Scaffolds `config/rbac/role_binding.yaml` with `kind: ClusterRoleBinding`\n- With `--force`: Regenerates `config/manager/manager.yaml` without WATCH_NAMESPACE env var\n\n**Manual steps required:**\n1. Remove `namespace=` parameter from RBAC markers in all controller files\n2. Run `make manifests` to regenerate cluster-scoped RBAC\n3. Remove namespace-scoped code from `cmd/main.go`:\n   - Remove `getWatchNamespace()` function\n   - Remove `setupCacheNamespaces()` function\n   - Remove namespace retrieval and cache configuration\n   - Remove added imports (`fmt`, `strings`, `cache`) if not used elsewhere\n4. If you didn't use `--force`, manually remove `WATCH_NAMESPACE` from `config/manager/manager.yaml`\n\n## Important Notes\n\n- **Only controllers need RBAC updates**: Only update `+kubebuilder:rbac` markers in controller files (files with `Reconcile` function). Webhook files do NOT use RBAC markers - webhooks use certificate-based authentication with the API server.\n- **RBAC markers control scope**: The `namespace=` parameter in controller RBAC markers determines whether controller-gen generates `Role` (namespace-scoped) or `ClusterRole` (cluster-scoped). Without the `namespace=` parameter, controller-gen always generates `ClusterRole`.\n- **Controller-gen regenerates role.yaml**: After running `make manifests`, controller-gen will regenerate `config/rbac/role.yaml` based on your controller RBAC markers. The initial `Role` scaffold from `kubebuilder edit --namespaced=true` serves as a template, but controller-gen manages the actual content.\n- **Namespace parameter format**: Use `namespace=<your-namespace>` in controller RBAC markers, typically `namespace=<project-name>-system` to match your deployment namespace.\n- **Metrics auth role stays cluster-scoped**: The `metrics-auth-role` uses cluster-scoped APIs (TokenReview, SubjectAccessReview) and correctly remains a ClusterRole without namespace parameter.\n- **Webhooks require manual configuration**: Currently, controller-gen does not support `namespaceSelector` or `objectSelector` markers for webhooks. See the webhook section above for details.\n\n## See Also\n\n- [Manager Scope](../reference/manager-scope.md) - Detailed explanation of manager scope concepts\n- [Project Config](../reference/project-config.md) - PROJECT file configuration reference\n"
  },
  {
    "path": "docs/book/src/migration/port-code.md",
    "content": "# Step 3: Port Custom Code\n\nAfter reorganizing your project (Step 1) and executing scaffolding commands from discovery (Step 2), use AI to port your custom code to the new project.\n\n<aside class=\"warning\">\n\n<h1>Important: Best Effort Only</h1>\n\nThe AI instructions below are provided as an **example** to help you get started. Due to the complexity and variety of Kubebuilder projects, we **cannot guarantee** it will work perfectly for all projects or be 100% accurate.\n\nYou should:\n- Adapt the instructions to your specific use case\n- **Validate ALL changes** made by AI carefully\n- Be prepared to manually fix issues\n- Not rely 100% on AI for correctness\n\nThe instructions may help you understand how to approach certain migration scenarios, but you remain responsible for ensuring correctness.\n\n</aside>\n\n<aside class=\"warning\">\n\n<h1>Prerequisites</h1>\n\nBefore using these AI instructions:\n1. You've reorganized your project using Step 1 (`make build` succeeds)\n2. You've backed up the reorganized project to `../migration-backup/`\n3. You've discovered and executed all scaffolding commands from Step 2\n4. `make build` succeeds in the new scaffolded project\n\n</aside>\n\n## Instructions to provide to your AI assistant\n\nCopy and paste these instructions to your AI assistant:\n\n```\nPort custom code from Kubebuilder project backup to new scaffolded project.\n\nCONTEXT:\nWhat is scaffold vs custom:\n- Scaffold: Auto-generated boilerplate by Kubebuilder (has \"// TODO(user):\" comments)\n- Custom: Your business logic that replaces TODOs\n\nBackup location: ../migration-backup/ (your old project with custom code)\nNew project: . (newly scaffolded project with TODOs to replace)\n\nHow to recognize each file type (by content, not just name):\n\nAPI files (typically *_types.go):\n- Have marker: // +kubebuilder:object:root=true\n- Have structs: type <Name> struct with metav1.TypeMeta, metav1.ObjectMeta\n- Have: <Name>Spec struct (desired state)\n- Have: <Name>Status struct (observed state)\n- Markers like: // +kubebuilder:validation:...\n\nController files (typically *_controller.go):\n- Have struct: type <Name>Reconciler struct { client.Client; Scheme *runtime.Scheme }\n- Have function: func (r *<Name>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)\n- Have function: func (r *<Name>Reconciler) SetupWithManager(mgr ctrl.Manager) error\n- May have: // +kubebuilder:rbac markers before Reconcile\n\nWebhook files (typically *_webhook.go):\n- OLD pattern: func (r *<Name>) Default(), func (r *<Name>) ValidateCreate() error\n- NEW pattern: type <Name>CustomDefaulter struct, func (d *<Name>CustomDefaulter) Default(ctx context.Context, obj *<Name>) error\n- Conversion: func (*<Name>) Hub(), func (r *<Name>) ConvertTo(...), func (r *<Name>) ConvertFrom(...)\n\nMain file:\n- Has: func main()\n- Has: ctrl.NewManager(...)\n- Registers controllers and webhooks\n\nFile paths after Step 1:\n- APIs in: api/v1/ or api/<group>/v1/\n- Controllers in: internal/controller/ or internal/controller/<group>/\n- Webhooks in: internal/webhook/v1/ or internal/webhook/<group>/v1/\n- Main: cmd/main.go\n\nFiles to NEVER edit (auto-generated):\n- config/crd/bases/*.yaml (generated from make manifests)\n- config/rbac/role.yaml (generated from make manifests)\n- config/webhook/manifests.yaml (generated from make manifests)\n- **/zz_generated.*.go (generated from make generate)\n- PROJECT file (managed by CLI)\n\nCritical markers to NEVER remove:\n- // +kubebuilder:scaffold:* (Kubebuilder injects code here)\n\nMake command sequence:\n- After editing APIs or markers: make generate && make manifests\n- After editing Go code: make build\n- After all changes: make lint-fix && make generate && make manifests && make all && make test\n\nCommon markers in API files:\n- // +kubebuilder:validation:Required\n- // +kubebuilder:validation:Minimum=1\n- // +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n- // +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=...\n\nRBAC markers in controller files:\n- // +kubebuilder:rbac:groups=<group>,resources=<resource>,verbs=get;list;watch;create;update;patch;delete\n- // +kubebuilder:rbac:groups=<group>,resources=<resource>/status,verbs=get;update;patch\n- // +kubebuilder:rbac:groups=<group>,resources=<resource>/finalizers,verbs=update\n\nReferences:\n- Kubebuilder Book: https://book.kubebuilder.io\n- Markers Reference: https://book.kubebuilder.io/reference/markers.html\n- controller-runtime: https://github.com/kubernetes-sigs/controller-runtime\n- controller-tools: https://github.com/kubernetes-sigs/controller-tools\n\nPORT CUSTOM CODE (in this order):\n\n1. Port go.mod dependencies FIRST:\n\n   Compare ../migration-backup/go.mod with current go.mod\n\n   a. For packages in backup but NOT in new (exclude k8s.io/*, sigs.k8s.io/controller-*):\n      - Run: go get <package>@<version>\n\n   b. For packages in BOTH with different versions:\n      - Keep the HIGHER (newer) version\n      - If backup has newer version: go get <package>@<newer-version>\n      - If new scaffold has newer version: keep it (don't downgrade)\n      - NOTE: Old projects can have newer versions than scaffold\n\n   After ALL: run go mod tidy\n\n2. Port API type definitions:\n\n   For each *_types.go in backup to new (paths match after Step 1):\n   Backup: ../migration-backup/api/v1/<kind>_types.go\n   New: api/v1/<kind>_types.go\n\n   Port:\n   - Custom fields in Spec and Status structs\n   - ALL +kubebuilder markers (validation, printcolumn, resource, etc.)\n   - Documentation comments\n   - Custom types (enums, type aliases)\n   - REMOVE \"// TODO(user):\" comments when adding fields\n\n   NEVER remove: // +kubebuilder:scaffold:* or // +kubebuilder:object:root=true\n\n   After each: go mod tidy && make generate && make manifests\n\n3. Port controller implementations:\n\n   For each controller (paths match after Step 1):\n   Backup: ../migration-backup/internal/controller/<kind>_controller.go\n   New: internal/controller/<kind>_controller.go\n\n   Port in order:\n   a. Additional imports (ADD to existing)\n   b. Custom constants, variables, types, interfaces (before Reconciler struct)\n   c. Custom fields in <Kind>Reconciler struct\n   d. ALL +kubebuilder:rbac markers (place before Reconcile)\n   e. Reconcile() body (REMOVE \"// TODO(user):\" and paste custom logic)\n   f. ALL helper functions (closures and standalone)\n   g. SetupWithManager customizations (if any beyond default .For().Named().Complete())\n\n   After each: go mod tidy && make generate && make manifests && make build\n\n4. Port webhooks:\n\n   CRITICAL: Code pattern depends on controller-runtime version!\n\n   Webhooks (paths match after Step 1):\n   Backup: ../migration-backup/internal/webhook/v1/<kind>_webhook.go\n   New: internal/webhook/v1/<kind>_webhook.go\n\n   Detect pattern by reading backup file:\n   - Has \"func (r *<Kind>) Default() {\": OLD pattern (needs adaptation)\n   - Has \"func (d *<Kind>CustomDefaulter) Default(ctx\": NEW pattern (direct copy)\n\n   IF OLD pattern - ADAPT:\n   - Default(): Extract logic, paste after type assertion, change 'r.' to '<kind>.', add return nil, REMOVE TODO\n   - Validate*(): Extract logic, paste after assertion, change 'r.' to '<kind>.', change return types, REMOVE TODO\n   - Conversion: Copy Hub/ConvertTo/ConvertFrom directly (no change needed)\n\n   IF NEW pattern - DIRECT COPY:\n   - Copy CustomDefaulter/CustomValidator structs and all methods\n   - Copy helper functions and imports\n\n   After each: go mod tidy && make manifests && make build\n\n5. Port main.go customizations:\n\n   Backup: ../migration-backup/cmd/main.go\n   New: cmd/main.go\n\n   Compare and port ONLY custom additions:\n   - Custom manager options\n   - Custom command-line flags\n   - Custom initialization before mgr.Start()\n   - Additional scheme registrations\n\n   DO NOT port standard scaffold (controller/webhook setup, manager config)\n\n   After: make build\n\n6. Port config settings (ADAPT, don't copy):\n\n   a. config/default/kustomization.yaml - Compare and adapt:\n      - Uncomment webhook/certmanager if you have webhooks\n      - Update namespace/namePrefix if custom\n      - Match metrics configuration\n      - Add custom patches/resources\n      DO NOT copy entire file\n\n   b. Other config/*/kustomization.yaml - Check for custom patches, adapt if needed\n\n   c. Custom config dirs - Copy any additional dirs: config/dev/, config/prod/, etc.\n\n   After: make build-installer\n\n7. Port config samples and customizations:\n   - Sample CRs: Copy ../migration-backup/config/samples/*.yaml to config/samples/\n   - Makefile: Copy custom targets from backup (preserve scaffolded targets)\n   - Dockerfile: Apply custom build steps from backup\n\n8. Port ALL tests:\n   - Controller tests: Copy *_controller_test.go from backup\n   - Webhook tests: Copy *_webhook_test.go (adapt if pattern changed)\n   - E2E tests: Copy test/e2e/* if exist\n   - Integration tests: Copy test/integration/* if exist\n\n9. Port additional files:\n   - README: Port custom sections (don't replace entire file)\n   - Additional dirs: Copy docs/, scripts/, examples/, charts/, testdata/ if exist\n   - Root files: Copy .env, VERSION, CHANGELOG.md, CONTRIBUTING.md if exist\n   - .github workflows: Copy custom workflows\n\n   DO NOT port: dist/, bin/, vendor/\n\n10. Verify nothing missed:\n   - Run: diff -r --brief ../migration-backup/ . | grep \"Only in ../migration-backup\"\n   - Port any custom files found (ignore: .git/, bin/, vendor/, dist/, zz_generated.*, go.sum, auto-gen configs)\n   - Verify key files have custom code (APIs, controllers, webhooks)\n\n11. Final verification:\n   - Run: go mod tidy\n   - Run: make lint-fix\n   - Run: make generate\n   - Run: make manifests\n   - Run: make build\n   - Run: make build-installer\n   - Run: make test\n\n   Success: no errors, tests pass, functionally identical to backup\n\nIMPORTANT REMINDERS:\n- NEVER edit auto-generated files (already listed in CONTEXT above)\n- NEVER remove // +kubebuilder:scaffold:* comments\n- REMOVE \"// TODO(user):\" when replacing with custom code\n- ADAPT config YAML files, don't copy entire files\n- Port EVERYTHING except: .git/, bin/, vendor/, dist/, zz_generated.*, go.sum\n- Follow make command sequence from CONTEXT above\n```\n\n## What AI Will Do\n\nThe AI will:\n\n1. **Detect layouts** - Compare old and new project structures\n2. **Port API definitions** - Custom fields, markers, documentation\n3. **Port controller logic** - Imports, types, Reconcile(), helpers, RBAC, SetupWithManager\n4. **Adapt webhooks** - Handle pattern changes if needed, port all logic and helpers\n5. **Port main.go** - Only custom initialization, flags, and manager options\n6. **Port configs** - kustomization.yaml, samples, Makefile, Dockerfile\n7. **Port dependencies** - Add packages to go.mod, run go mod tidy\n8. **Port tests** - Controller tests, webhook tests, e2e tests, integration tests\n9. **Port additional files** - README, docs/, scripts/, .github/, any custom directories\n10. **Verify completely** - Run lint-fix, generate, manifests, build, test\n\n## After AI Completes\n\n**Critical: Review carefully!**\n\n<aside class=\"warning\">\n\n<h1>Manual Review Required</h1>\n\nAfter AI ports the code:\n\n1. **Review webhook implementations** - If migrating from v3, verify the pattern adaptation is correct\n2. **Check RBAC markers** - Ensure all permissions are preserved\n3. **Test custom logic** - Run `make test` and verify your business logic works\n4. **Compare critical files** - Diff API types, controller logic, webhook validation\n5. **Test in a cluster** - Deploy and verify actual behavior\n\nAI can make mistakes. You are responsible for ensuring correctness.\n\n</aside>\n\n## Example: What Gets Ported\n\n### API Custom Fields\n\n**From backup** (`api/v1/captain_types.go`):\n```go\ntype CaptainSpec struct {\n    // +kubebuilder:validation:Minimum=1\n    // +kubebuilder:validation:Maximum=100\n    Replicas int32 `json:\"replicas\"`\n\n    // +kubebuilder:validation:Pattern=`^[a-z]+$`\n    Name string `json:\"name\"`\n}\n```\n\n**To new project** (TODO removed, custom fields added):\n```go\ntype CaptainSpec struct {\n    // +kubebuilder:validation:Minimum=1\n    // +kubebuilder:validation:Maximum=100\n    Replicas int32 `json:\"replicas\"`\n\n    // +kubebuilder:validation:Pattern=`^[a-z]+$`\n    Name string `json:\"name\"`\n}\n```\n\n### Controller Reconcile Logic\n\n**From backup** (Reconcile function body):\n```go\nfunc (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n    log := log.FromContext(ctx)\n\n    // Your custom reconciliation logic here\n    var captain crewv1.Captain\n    if err := r.Get(ctx, req.NamespacedName, &captain); err != nil {\n        return ctrl.Result{}, client.IgnoreNotFound(err)\n    }\n\n    // Custom business logic...\n\n    return ctrl.Result{}, nil\n}\n```\n\n**To new project** (TODO removed, custom logic added):\n```go\nfunc (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n    log := log.FromContext(ctx)\n\n    // Custom reconciliation logic from backup\n    var captain crewv1.Captain\n    if err := r.Get(ctx, req.NamespacedName, &captain); err != nil {\n        return ctrl.Result{}, client.IgnoreNotFound(err)\n    }\n\n    // Custom business logic...\n\n    return ctrl.Result{}, nil\n}\n```\n\n### Webhook Adaptation (v3 to v4)\n\n**From go/v3 backup**:\n```go\nfunc (r *Captain) Default() {\n    if r.Spec.Replicas == 0 {\n        r.Spec.Replicas = 1\n    }\n}\n```\n\n**To go/v4 new project**:\n```go\nfunc (d *CaptainCustomDefaulter) Default(ctx context.Context, obj *crewv1.Captain) error {\n    // Ported logic adapted (obj is type-safe, no assertion needed):\n    if obj.Spec.Replicas == 0 {\n        obj.Spec.Replicas = 1\n    }\n\n    return nil\n}\n```\n\n## Next Steps\n\nAfter AI ports your code:\n\n1. Check if nothing is missed, broken or wrongly ported\n2. Deploy to test cluster - Verify behavior\n\n<aside class=\"note\">\n\n<h1>If You Have a Helm Chart</h1>\n\nIf you had a Helm chart to distribute your project, you may want to consider regenerate with the [helm/v2-alpha plugin](../plugins/available/helm-v2-alpha.md)\nand then applying your customizations on top.\n\n</aside>"
  },
  {
    "path": "docs/book/src/migration/reorganize-layout.md",
    "content": "# Step 1: Reorganize to New Layout (Required only for Legacy Layouts)\n\n**If your project was built with Kubebuilder prior to v3.0.0**, you will probably need this step.\n\nReorganize files to match the new directory layout.\n\n**Check if you need this step (if ANY are true, you need this):**\n- Controllers are NOT in `internal/controller/`\n- Webhooks are NOT in `internal/webhook/`\n- Main is NOT in `cmd/`\n\n**If ALL are already in the new layout**, skip to [Step 2](./discovery-commands.md)\n\n<aside class=\"warning\">\n\n<h1>Important: Best Effort Only</h1>\n\nThese AI instructions work for projects using **standard Kubebuilder directory layout**. Projects with heavily customized structures may require manual reorganization.\n\nDue to the variety of customizations, we **cannot guarantee** it will work perfectly for all projects.\n\nAlways validate changes carefully.\n\n</aside>\n\n## Instructions to provide to your AI assistant\n\nCopy and paste these instructions to your AI assistant:\n\n```\nReorganize Kubebuilder project files to match new directory layout.\n\nCONTEXT:\n- Project location: . (current directory - your existing project)\n- Goal: Move files to new layout WITHOUT changing code or versions\n- Keep project functional after reorganization\n\nSTEP 1 - Check which files need to move:\n- Controllers in controllers/ or pkg/controllers/: needs move\n- Controllers in internal/controller/ or internal/controller/<group>/ (multi-group): already correct\n- Webhooks in api/v1/ or apis/<group>/v1/: needs move\n- Webhooks in internal/webhook/v1/ or internal/webhook/<group>/v1/ (multi-group): already correct\n- Main in root (main.go): needs move\n- Main in cmd/ (cmd/main.go or cmd/*/main.go): already correct\n\nSTEP 2 - Reorganize file locations:\n\na. Move controllers if needed:\n   - If controllers/ directory exists:\n     mkdir -p internal/controller\n     mv controllers/* internal/controller/\n     rmdir controllers\n   - If pkg/controllers/ directory exists:\n     mkdir -p internal/controller\n     mv pkg/controllers/* internal/controller/\n\nb. Move webhooks if needed:\n   - If api/v1/ or apis/v1/ contains *_webhook.go files:\n     mkdir -p internal/webhook/v1\n     mv api/v1/*_webhook* internal/webhook/v1/ 2>/dev/null || mv apis/v1/*_webhook* internal/webhook/v1/ 2>/dev/null || true\n   - If api/<group>/v1/ or apis/<group>/v1/ contains webhooks (multi-group):\n     mkdir -p internal/webhook/<group>/v1\n     mv api/<group>/v1/*_webhook* internal/webhook/<group>/v1/ 2>/dev/null || mv apis/<group>/v1/*_webhook* internal/webhook/<group>/v1/ 2>/dev/null || true\n\nc. Move main.go if needed:\n   - If main.go exists in root:\n     mkdir -p cmd\n     mv main.go cmd/\n\nSTEP 3 - Update import paths in ALL files:\n\nAfter moving files, imports will break. Fix them systematically:\n\na. In cmd/main.go (or cmd/*/main.go, cmd/*/*.go):\n   - Find: import \"your-module/controllers\"\n   - Replace with: import \"your-module/internal/controller\"\n   - Find: import \"your-module/pkg/controllers\"\n   - Replace with: import \"your-module/internal/controller\"\n   - Find: &controllers.SomeReconciler or controllers.NewController\n   - Replace with: &controller.SomeReconciler or controller.NewController\n   - API imports (api/v1, apis/v1alpha1) - NO CHANGE needed\n\nb. In internal/controller/*.go files:\n   - Check package declaration is still: package controller (not controllers)\n   - API imports stay same - NO CHANGE needed\n   - If you had controller-to-controller imports, update paths\n\nc. In internal/webhook/v1/*.go files:\n   - Check package declaration: should be package v1\n   - API imports stay same - NO CHANGE needed\n   - Webhook imports in main.go may need updating\n\nSTEP 4 - Update Dockerfile (if using explicit COPY):\n\nCheck Dockerfile for explicit COPY statements. If found, update:\n\nOld pattern:\n    COPY cmd/main.go cmd/main.go\n    COPY api/ api/\n    COPY internal/controller/ internal/controller/\n\nOption 1 - Simplify (recommended):\n    COPY . .\n\nEnsure .dockerignore has:\n    **\n    !**/*.go\n    **/*_test.go\n    !go.mod\n    !go.sum\n\nOption 2 - Update explicit paths:\n    COPY cmd/ cmd/\n    COPY api/ api/\n    COPY internal/ internal/\n\nSTEP 5 - Verify reorganization:\n\n- Run: go mod tidy\n- Run: make generate\n- Run: make manifests\n- Run: make build\n- Run: make test\n\nIf errors, fix import paths.\n\nSuccess: new layout, make build succeeds, make test passes, project functional\n```\n\n## What This Does\n\nThe AI will:\n\n1. **Move files** to new layout (controllers/ to internal/controller/, webhooks to internal/webhook/, main.go to cmd/)\n2. **Fix import paths** in all files after moves\n3. **Verify** the reorganized project builds and tests pass\n\nAfter this step, your project uses the new layout (same code, new locations), making migration much simpler!\n\n## Next Steps\n\nAfter AI reorganizes:\n\n1. Verify: `make build && make test` (in current project)\n2. If successful, backup and proceed to [Step 2: Discovery CLI Commands](./discovery-commands.md)\n3. If errors, review and fix before proceeding\n\n\n"
  },
  {
    "path": "docs/book/src/migrations.md",
    "content": "# Migrations\n\nUpgrading your Kubebuilder project to the latest version ensures you benefit from new features,\nbug fixes, and ecosystem improvements. It is recommended to keep your project aligned with ecosystem changes.\n\nMigration may involve updating to a newer plugin version (e.g., from `go.kubebuilder.io/v3` in release 3.x to `go.kubebuilder.io/v4` in release 4.x) or updating the scaffold produced by the same plugin across CLI releases (e.g., from `v4.9.0` to `v4.10.1`).\n\nKubebuilder provides multiple migration paths to suit your workflow. Choose the approach that best fits your needs.\n\n<aside class=\"note\">\n<h1>Understanding the PROJECT File</h1>\n\nFrom Kubebuilder `v3.0.0` onwards, all inputs used by Kubebuilder are tracked in the [PROJECT][project-config] file.\nIf you use the CLI to generate your scaffolds, this file will record the project's configuration and metadata,\nenabling all automation tools to work effectively.\n\nIt is recommended to use the CLI to scaffold all resources (`kubebuilder create api`, `kubebuilder create webhook`, etc.)\nwhenever possible, including controllers and webhooks for external types. The CLI has been continuously improved\nover time to address various options and needs. This ensures all resources are tracked in the PROJECT file,\nwhich automation tools (alpha update, alpha generate, autoupdate plugin) depend on.\n\n</aside>\n\n<aside class=\"warning\">\n<h1>Project Customizations</h1>\n\nAfter using the CLI to create your project, you are free to customize the business logic and add features as you see fit.\nHowever, it is not recommended to deviate from the proposed project layout unless you know what you are doing.\n\nFor example, you should refrain from moving the scaffolded files, as doing so may will make it difficult to upgrade\nyour project in the future. You may also lose the ability to use some of the CLI features and helpers.\n\nProjects that do not use the CLI to generate scaffolds, or that deviate heavily from the proposed layout,\nmay need to use the manual migration process, as automated migration tools might not work properly while\nthe [alpha update](./reference/commands/alpha_update.md) and [AutoUpdate Plugin][autoupdate-v1-alpha]\nare designed to do a 3-way merge to keep your customizations intact.\n\nFor further information on the project layout, see [What's in a basic project?][basic-project-doc]\n\n</aside>\n\n## Migration Options\n\n\n> [!TIP]\n> To reduce effort, we recommend enabling the [AutoUpdate Plugin][autoupdate-v1-alpha] (GitHub Actions). You can also run [alpha update](./reference/commands/alpha_update.md) locally—both use the same update logic. Use the other options mainly for older projects that do not have `cliVersion` in the `PROJECT` file as a one-time step to reach a supported version; after that, use these workflows for future updates (older versions cannot use these automation features).\n\n###  **(Recommended)** AutoUpdate/GitHub Action: Get Notified of New Kubebuilder Releases via Issues with a PR Link to Review and Upgrade\n\nThe [AutoUpdate Plugin][autoupdate-v1-alpha] scaffolds an action that automatically monitors for new Kubebuilder releases and\nopens a GitHub Issue with a Pull Request compare link when updates are available. This is ideal for\nkeeping your project up to date with minimal manual work.\n\nThis plugin provides a mechanism similar to Dependabot for GitHub, offering continuous updates with AI assistance\nfor projects that follow the standard scaffold.\n\n```bash\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\"\n```\n\n<aside class=\"note\">\n<h1>Requirements and Limitations</h1>\n\n- Requires GitHub repository (GitHub Actions workflow)\n- Requires branch protection rules for safety (recommended)\n- Needs the same requirements as `alpha update` (see below)\n\n</aside>\n\nSee the [AutoUpdate Plugin documentation][autoupdate-v1-alpha] for complete details.\n\n### **(Recommended)** Use `alpha update` to Upgrade Without Losing Customisations (Logic Behind AutoUpdate/GitHub Action)\n\nIf you prefer to run updates locally instead of relying on GitHub Actions, you can use the same logic\nas the [AutoUpdate Plugin][autoupdate-v1-alpha] directly from your command line.\n\n```shell\nkubebuilder alpha update\n```\n\nThis command uses the same underlying mechanism as the AutoUpdate Plugin. You can migrate your project,\nresolve any conflicts if needed, and then push a Pull Request from your local environment.\n\n<aside class=\"note\">\n<h1>Requirements and Limitations</h1>\n\n- Requires projects created with Kubebuilder **`v4.5.0`** or later\n- For projects created before `v4.6.0`: the CLI version is not tracked in the `PROJECT` file, so you may need to use `alpha generate` first to establish a baseline\n- For projects created with `v4.6.0`+: includes `cliVersion` in the `PROJECT` file for automatic version detection\n\n</aside>\n\nSee the [`alpha update` command reference](./reference/commands/alpha_update.md) for all options and flags.\n\n### Regenerate with Help and Merge Manually\n\nThe `kubebuilder alpha generate` command re-scaffolds your entire project based on your `PROJECT` file\nconfiguration. You can then manually compare and merge your custom code. For example, you can use it to\nregenerate your project after upgrading the Kubebuilder CLI version and then, manually use an IDE or\n`git diff` to compare and merge changes by hand into your existing codebase to ensure that all your changes\nare applied in a new scaffold.\n\nThis approach is useful for projects that heavily customize the scaffold or\nwhen other migration methods aren't available. You might need to use this method only once to\nestablish a baseline for future automated updates.\n\n```shell\nkubebuilder alpha generate\n```\n\n<aside class=\"note\">\n<h1>Requirements and Limitations</h1>\n\n- Requires a `PROJECT` file (projects created with Kubebuilder **v3.0.0** or later)\n- Only re-scaffolds resources that were created using the CLI and tracked in the `PROJECT` file\n- Manually created APIs, controllers, or webhooks will not be regenerated\n- This may result in a partial re-scaffold if you have manually created resources\n- Requires manual comparison and merge of custom code after regeneration\n\n</aside>\n\nSee the [`alpha generate` command reference](./reference/commands/alpha_generate.md) for details.\n\n### Fully Manual Migration\n\nFor complete control, you can manually migrate by creating a new project with the latest Kubebuilder\nversion and porting your code over.\n\nIn this process, you will run all commands from scratch to create a new project, APIs, controllers,\nwebhooks, and other resources. Then, manually copy your business logic and customizations from your old project to the new one.\n\nTo streamline this one-time migration, [AI Migration Helpers](./migration/ai-helpers.md) have been added to automate repetitive tasks.\n\n<aside class=\"note\">\n<h1>When to Use Manual Migration</h1>\n\nUse this approach when:\n- Your project was created with Kubebuilder versions **before `v3.0.0`** (no `PROJECT` file)\n- You have heavily customized the scaffold beyond standard patterns\n- You have manually created APIs, controllers, or webhooks not tracked by the CLI\n- You want complete control and visibility into every change\n- Other automated methods are not available for your project version\n\n</aside>\n\nSee the [Manual Migration Process Guide](./migration/manual-process.md) for a complete step-by-step walkthrough with AI helpers.\n\n[project-config]: ./reference/project-config.md\n[basic-project-doc]: ./cronjob-tutorial/basic-project.md\n[autoupdate-v1-alpha]: ./plugins/available/autoupdate-v1-alpha.md"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/api-changes.md",
    "content": "# Changing things up\n\nA fairly common change in a Kubernetes API is to take some data that used\nto be unstructured or stored in some special string format, and change it\nto structured data.   Our `schedule` field fits the bill quite nicely for\nthis -- right now, in `v1`, our schedules look like\n\n```yaml\nschedule: \"*/1 * * * *\"\n```\n\nThat's a pretty textbook example of a special string format (it's also\npretty unreadable unless you're a Unix sysadmin).\n\nLet's make it a bit more structured.  According to our [CronJob\ncode][cronjob-sched-code], we support \"standard\" Cron format.\n\nIn Kubernetes, **all versions must be safely round-tripable through each\nother**.  This means that if we convert from version 1 to version 2, and\nthen back to version 1, we must not lose information.  Thus, any change we\nmake to our API must be compatible with whatever we supported in v1, and\nalso need to make sure anything we add in v2 is supported in v1.  In some\ncases, this means we need to add new fields to v1, but in our case, we\nwon't have to, since we're not adding new functionality.\n\nKeeping all that in mind, let's convert our example above to be\nslightly more structured:\n\n```yaml\nschedule:\n  minute: */1\n```\n\nNow, at least, we've got labels for each of our fields, but we can still\neasily support all the different syntax for each field.\n\nWe'll need a new API version for this change.  Let's call it v2:\n\n```shell\nkubebuilder create api --group batch --version v2 --kind CronJob\n```\n\nPress `y` for \"Create Resource\" and `n` for \"Create Controller\".\n\nNow, let's copy over our existing types, and make the change:\n\n{{#literatego ./testdata/project/api/v2/cronjob_types.go}}\n\n## Storage Versions\n\n{{#literatego ./testdata/project/api/v1/cronjob_types.go}}\n\nNow that we've got our types in place, we'll need to set up conversion...\n\n[cronjob-sched-code]: ./multiversion-tutorial/testdata/project/api/v2/cronjob_types.go \"CronJob Code\"\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/conversion-concepts.md",
    "content": "# Hubs, spokes, and other wheel metaphors\n\nSince we now have two different versions, and users can request either\nversion, we'll have to define a way to convert between our version. For\nCRDs, this is done using a webhook, similar to the defaulting and\nvalidating webhooks we [defined in the base\ntutorial](/cronjob-tutorial/webhook-implementation.md).  Like before,\ncontroller-runtime will help us wire together the nitty-gritty bits, we\njust have to implement the actual conversion.\n\nBefore we do that, though, we'll need to understand how controller-runtime\nthinks about versions.  Namely:\n\n## Complete graphs are insufficiently nautical\n\nA simple approach to defining conversion might be to define conversion\nfunctions to convert between each of our versions.  Then, whenever we need\nto convert, we'd look up the appropriate function, and call it to run the\nconversion.\n\nThis works fine when we just have two versions, but what if we had\n4 types? 8 types? That'd be a lot of conversion functions.\n\nInstead, controller-runtime models conversion in terms of a \"hub and\nspoke\" model -- we mark one version as the \"hub\", and all other versions\njust define conversion to and from the hub:\n\n<!-- include these inline so we can style an match variables -->\n<div class=\"diagrams\">\n{{#include ./complete-graph-8.svg}}\n<div>becomes</div>\n{{#include ./hub-spoke-graph.svg}}\n</div>\n\nThen, if we have to convert between two non-hub versions, we first convert\nto the hub version, and then to our desired version:\n\n<div class=\"diagrams\">\n{{#include ./conversion-diagram.svg}}\n</div>\n\nThis cuts down on the number of conversion functions that we have to\ndefine, and is modeled off of what Kubernetes does internally.\n\n## What does that have to do with Webhooks?\n\nWhen API clients, like kubectl or your controller, request a particular\nversion of your resource, the Kubernetes API server needs to return\na result that's of that version.  However, that version might not match\nthe version stored by the API server.\n\nIn that case, the API server needs to know how to convert between the\ndesired version and the stored version.  Since the conversions aren't\nbuilt in for CRDs, the Kubernetes API server calls out to a webhook to do\nthe conversion instead.  For Kubebuilder, this webhook is implemented by\ncontroller-runtime, and performs the hub-and-spoke conversions that we\ndiscussed above.\n\nNow that we have the model for conversion down pat, we can actually\nimplement our conversions.\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/conversion.md",
    "content": "# Implementing conversion\n\nWith our model for conversion in place, it's time to actually implement\nthe conversion functions.  We'll create a conversion webhook\nfor our CronJob API version `v1` (Hub) to Spoke our CronJob API version\n`v2` see:\n\n```go\nkubebuilder create webhook --group batch --version v1 --kind CronJob --conversion --spoke v2\n```\n\nThe above command will generate the `cronjob_conversion.go` next to our\n`cronjob_types.go` file, to avoid\ncluttering up our main types file with extra functions.\n\n<aside class=\"note\">\n<h1>Conversion Webhooks and Custom Paths</h1>\n\nUnlike defaulting and validation webhooks, conversion webhooks do not support custom paths\nvia command-line flags. Conversion webhooks use CRD conversion configuration\n(`.spec.conversion.webhook.clientConfig.service.path` in the CRD) rather than webhook\nmarker annotations. The path for conversion webhooks is managed differently and cannot\nbe customized through kubebuilder flags.\n</aside>\n\n## Hub...\n\nFirst, we'll implement the hub.  We'll choose the v1 version as the hub:\n\n{{#literatego ./testdata/project/api/v1/cronjob_conversion.go}}\n\n## ... and Spokes\n\nThen, we'll implement our spoke, the v2 version:\n\n{{#literatego ./testdata/project/api/v2/cronjob_conversion.go}}\n\nNow that we've got our conversions in place, all that we need to do is\nwire up our main to serve the webhook!\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/deployment.md",
    "content": "# Deployment and Testing\n\nBefore we can test out our conversion, we'll need to enable them in our CRD:\n\nKubebuilder generates Kubernetes manifests under the `config` directory with webhook\nbits disabled. To enable them, we need to:\n\n- Enable `patches/webhook_in_<kind>.yaml` and\n  `patches/cainjection_in_<kind>.yaml` in\n  `config/crd/kustomization.yaml` file.\n\n- Enable `../certmanager` and `../webhook` directories under the\n  `bases` section in `config/default/kustomization.yaml` file.\n\n- Enable all the vars under the `CERTMANAGER` section in\n  `config/default/kustomization.yaml` file.\n\nAdditionally, if present in our Makefile, we'll need to set the `CRD_OPTIONS` variable to just\n`\"crd\"`, removing the `trivialVersions` option (this ensures that we\nactually [generate validation for each version][ref-multiver], instead of\ntelling Kubernetes that they're the same):\n\n```makefile\nCRD_OPTIONS ?= \"crd\"\n```\n\nNow we have all our code changes and manifests in place, so let's deploy it to\nthe cluster and test it out.\n\nYou'll need [cert-manager](../cronjob-tutorial/cert-manager.md) installed\n(version `0.9.0+`) unless you've got some other certificate management\nsolution.  The Kubebuilder team has tested the instructions in this tutorial\nwith\n[0.9.0-alpha.0](https://github.com/cert-manager/cert-manager/releases/tag/v0.9.0-alpha.0)\nrelease.\n\nOnce all our ducks are in a row with certificates, we can run `make\ninstall deploy` (as normal) to deploy all the bits (CRD,\ncontroller-manager deployment) onto the cluster.\n\n## Testing\n\nOnce all of the bits are up and running on the cluster with conversion enabled, we can test out our\nconversion by requesting different versions.\n\nWe'll make a v2 version based on our v1 version (put it under `config/samples`)\n\n```yaml\n{{#include ./testdata/project/config/samples/batch_v2_cronjob.yaml}}\n```\n\nThen, we can create it on the cluster:\n\n```shell\nkubectl apply -f config/samples/batch_v2_cronjob.yaml\n```\n\nIf we've done everything correctly, it should create successfully,\nand we should be able to fetch it using both the v2 resource\n\n```shell\nkubectl get cronjobs.v2.batch.tutorial.kubebuilder.io -o yaml\n```\n\n```yaml\n{{#include ./testdata/project/config/samples/batch_v2_cronjob.yaml}}\n```\n\nand the v1 resource\n\n```shell\nkubectl get cronjobs.v1.batch.tutorial.kubebuilder.io -o yaml\n```\n```yaml\n{{#include ./testdata/project/config/samples/batch_v1_cronjob.yaml}}\n```\n\nBoth should be filled out, and look equivalent to our v2 and v1 samples,\nrespectively.  Notice that each has a different API version.\n\nFinally, if we wait a bit, we should notice that our CronJob continues to\nreconcile, even though our controller is written against our v1 API version.\n\n<aside class=\"note\">\n\n<h1>kubectl and Preferred Versions</h1>\n\nWhen we access our API types from Go code, we ask for a specific version\nby using that version's Go type (e.g. `batchv2.CronJob`).\n\nYou might've noticed that the above invocations of kubectl looked\na little different from what we usually do -- namely, they specify\na *group-version-resource*, instead of just a resource.\n\nWhen we write `kubectl get cronjob`, kubectl needs to figure out which\ngroup-version-resource that maps to.  To do this, it uses the *discovery\nAPI* to figure out the preferred version of the `cronjob` resource.  For\nCRDs, this is more-or-less the latest stable version (see the [CRD\ndocs][CRD-version-pref] for specific details).\n\nWith our updates to CronJob, this means that `kubectl get cronjob` fetches\nthe `batch/v2` group-version.\n\nIf we want to specify an exact version, we can use `kubectl get\nresource.version.group`, as we do above.\n\n***You should always use fully-qualified group-version-resource syntax in\nscripts***.  `kubectl get resource` is for humans, self-aware robots, and\nother sentient beings that can figure out new versions.  `kubectl get\nresource.version.group` is for everything else.\n\n</aside>\n\n[ref-multiver]: /reference/generating-crd.md#multiple-versions \"Generating CRDs: Multiple Versions\"\n\n[crd-version-pref]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#version-priority \"Versions in CustomResourceDefinitions\"\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.github/workflows/test-chart.yml",
    "content": "name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare project\n        run: |\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n\n      - name: Install cert-manager via Helm (wait for readiness)\n        run: |\n          helm repo add jetstack https://charts.jetstack.io\n          helm repo update\n          helm install cert-manager jetstack/cert-manager \\\n            --namespace cert-manager \\\n            --create-namespace \\\n            --set crds.enabled=true \\\n            --wait \\\n            --timeout 300s\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Deploy manager via Helm\n        run: |\n          make helm-deploy IMG=project:v0.1.0\n\n      - name: Check Helm release status\n        run: |\n          make helm-status\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/AGENTS.md",
    "content": "# project - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t# Note that the option maxDescLen=0 was added in the default scaffold in order to sort out the issue\n\t# Too long: must have at most 262144 bytes. By using kubectl apply to create / update resources an annotation\n\t# is created by K8s API to store the latest version of the resource ( kubectl.kubernetes.io/last-applied-configuration).\n\t# However, it has a size limit and if the CRD is too big with so many long descriptions as this one it will cause the failure.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd:maxDescLen=0 webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-builder\n\t$(CONTAINER_TOOL) buildx use project-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= project-system\n## Name of the Helm release\nHELM_RELEASE ?= project\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= dist/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: tutorial.kubebuilder.io\nlayout:\n- go.kubebuilder.io/v4\nplugins:\n  helm.kubebuilder.io/v2-alpha:\n    manifests: dist/install.yaml\n    output: dist\nprojectName: project\nrepo: tutorial.kubebuilder.io/project\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: tutorial.kubebuilder.io\n  group: batch\n  kind: CronJob\n  path: tutorial.kubebuilder.io/project/api/v1\n  version: v1\n  webhooks:\n    conversion: true\n    defaulting: true\n    spoke:\n    - v2\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  domain: tutorial.kubebuilder.io\n  group: batch\n  kind: CronJob\n  path: tutorial.kubebuilder.io/project/api/v2\n  version: v2\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\nversion: \"3\"\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/README.md",
    "content": "# project\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v1\n\n/*\nImplementing the hub method is pretty easy -- we just have to add an empty\nmethod called `Hub()`to serve as a\n[marker](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub).\nWe could also just put this inline in our cronjob_types.go file.\n*/\n\n// Hub marks this type as a conversion hub.\nfunc (*CronJob) Hub() {}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v1/cronjob_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\n */\n\npackage v1\n\n/*\n */\n\nimport (\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n// CronJobSpec defines the desired state of CronJob\ntype CronJobSpec struct {\n\t// schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.\n\t// +kubebuilder:validation:MinLength=0\n\t// +required\n\tSchedule string `json:\"schedule\"`\n\n\t// startingDeadlineSeconds defines in seconds for starting the job if it misses scheduled\n\t// time for any reason.  Missed jobs executions will be counted as failed ones.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tStartingDeadlineSeconds *int64 `json:\"startingDeadlineSeconds,omitempty\"`\n\n\t// concurrencyPolicy specifies how to treat concurrent executions of a Job.\n\t// Valid values are:\n\t// - \"Allow\" (default): allows CronJobs to run concurrently;\n\t// - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet;\n\t// - \"Replace\": cancels currently running job and replaces it with a new one\n\t// +optional\n\t// +kubebuilder:default:=Allow\n\tConcurrencyPolicy ConcurrencyPolicy `json:\"concurrencyPolicy,omitempty\"`\n\n\t// suspend tells the controller to suspend subsequent executions, it does\n\t// not apply to already started executions.  Defaults to false.\n\t// +optional\n\tSuspend *bool `json:\"suspend,omitempty\"`\n\n\t// jobTemplate defines the job that will be created when executing a CronJob.\n\t// +required\n\tJobTemplate batchv1.JobTemplateSpec `json:\"jobTemplate\"`\n\n\t// successfulJobsHistoryLimit defines the number of successful finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tSuccessfulJobsHistoryLimit *int32 `json:\"successfulJobsHistoryLimit,omitempty\"`\n\n\t// failedJobsHistoryLimit defines the number of failed finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tFailedJobsHistoryLimit *int32 `json:\"failedJobsHistoryLimit,omitempty\"`\n}\n\n// ConcurrencyPolicy describes how the job will be handled.\n// Only one of the following concurrent policies may be specified.\n// If none of the following policies is specified, the default one\n// is AllowConcurrent.\n// +kubebuilder:validation:Enum=Allow;Forbid;Replace\ntype ConcurrencyPolicy string\n\nconst (\n\t// AllowConcurrent allows CronJobs to run concurrently.\n\tAllowConcurrent ConcurrencyPolicy = \"Allow\"\n\n\t// ForbidConcurrent forbids concurrent runs, skipping next run if previous\n\t// hasn't finished yet.\n\tForbidConcurrent ConcurrencyPolicy = \"Forbid\"\n\n\t// ReplaceConcurrent cancels currently running job and replaces it with a new one.\n\tReplaceConcurrent ConcurrencyPolicy = \"Replace\"\n)\n\n// CronJobStatus defines the observed state of CronJob.\ntype CronJobStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// active defines a list of pointers to currently running jobs.\n\t// +optional\n\t// +listType=atomic\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:MaxItems=10\n\tActive []corev1.ObjectReference `json:\"active,omitempty\"`\n\n\t// lastScheduleTime defines when was the last time the job was successfully scheduled.\n\t// +optional\n\tLastScheduleTime *metav1.Time `json:\"lastScheduleTime,omitempty\"`\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the CronJob resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:docs-gen:collapse=Remaining code from cronjob_types.go\n\n/*\n Since we'll have more than one version, we'll need to mark a storage version.\n This is the version that the Kubernetes API server uses to store our data.\n We'll chose the v1 version for our project.\n\n We'll use the [`+kubebuilder:storageversion`](/reference/markers/crd.md) to do this.\n\n Note that multiple versions may exist in storage if they were written before\n the storage version changes -- changing the storage version only affects how\n objects are created/updated after the change.\n*/\n\n// +kubebuilder:object:root=true\n// +kubebuilder:storageversion\n// +kubebuilder:subresource:status\n// +versionName=v1\n// +kubebuilder:storageversion\n// CronJob is the Schema for the cronjobs API\ntype CronJob struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of CronJob\n\t// +required\n\tSpec CronJobSpec `json:\"spec\"`\n\n\t// status defines the observed state of CronJob\n\t// +optional\n\tStatus CronJobStatus `json:\"status,omitzero\"`\n}\n\n/*\n */\n\n// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob\ntype CronJobList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []CronJob `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&CronJob{}, &CronJobList{})\n}\n\n// +kubebuilder:docs-gen:collapse=Remaining code from cronjob_types.go\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nFirst, we have some *package-level* markers that denote that there are\nKubernetes objects in this package, and that this package represents the group\n`batch.tutorial.kubebuilder.io`. The `object` generator makes use of the\nformer, while the latter is used by the CRD generator to generate the right\nmetadata for the CRDs it creates from this package.\n*/\n\n// Package v1 contains API Schema definitions for the batch v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=batch.tutorial.kubebuilder.io\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\n/*\nThen, we have the commonly useful variables that help us set up our Scheme.\nSince we need to use all the types in this package in our controller, it's\nhelpful (and the convention) to have a convenient method to add all the types to\nsome other `Scheme`. SchemeBuilder makes this easy for us.\n*/\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"batch.tutorial.kubebuilder.io\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJob) DeepCopyInto(out *CronJob) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJob.\nfunc (in *CronJob) DeepCopy() *CronJob {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJob)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJob) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobList) DeepCopyInto(out *CronJobList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]CronJob, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobList.\nfunc (in *CronJobList) DeepCopy() *CronJobList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJobList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) {\n\t*out = *in\n\tif in.StartingDeadlineSeconds != nil {\n\t\tin, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds\n\t\t*out = new(int64)\n\t\t**out = **in\n\t}\n\tif in.Suspend != nil {\n\t\tin, out := &in.Suspend, &out.Suspend\n\t\t*out = new(bool)\n\t\t**out = **in\n\t}\n\tin.JobTemplate.DeepCopyInto(&out.JobTemplate)\n\tif in.SuccessfulJobsHistoryLimit != nil {\n\t\tin, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n\tif in.FailedJobsHistoryLimit != nil {\n\t\tin, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobSpec.\nfunc (in *CronJobSpec) DeepCopy() *CronJobSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobStatus) DeepCopyInto(out *CronJobStatus) {\n\t*out = *in\n\tif in.Active != nil {\n\t\tin, out := &in.Active, &out.Active\n\t\t*out = make([]corev1.ObjectReference, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.LastScheduleTime != nil {\n\t\tin, out := &in.LastScheduleTime, &out.LastScheduleTime\n\t\t*out = (*in).DeepCopy()\n\t}\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobStatus.\nfunc (in *CronJobStatus) DeepCopy() *CronJobStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v2\n\n/*\nFor imports, we'll need the controller-runtime\n[`conversion`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc)\npackage, plus the API version for our hub type (v1), and finally some of the\nstandard packages.\n*/\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/conversion\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n) // +kubebuilder:docs-gen:collapse=Imports\n\n/*\nOur \"spoke\" versions need to implement the\n[`Convertible`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible)\ninterface. Namely, they'll need `ConvertTo()` and `ConvertFrom()`\nmethods to convert to/from the hub version.\n*/\n\n/*\nConvertTo is expected to modify its argument to contain the converted object.\nMost of the conversion is straightforward copying, except for converting our changed field.\n*/\n\n// ConvertTo converts this CronJob (v2) to the Hub version (v1).\nfunc (src *CronJob) ConvertTo(dstRaw conversion.Hub) error {\n\tdst := dstRaw.(*batchv1.CronJob)\n\tlog.Printf(\"ConvertTo: Converting CronJob from Spoke version v2 to Hub version v1;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\tsched := src.Spec.Schedule\n\tscheduleParts := []string{\"*\", \"*\", \"*\", \"*\", \"*\"}\n\tif sched.Minute != nil {\n\t\tscheduleParts[0] = string(*sched.Minute)\n\t}\n\tif sched.Hour != nil {\n\t\tscheduleParts[1] = string(*sched.Hour)\n\t}\n\tif sched.DayOfMonth != nil {\n\t\tscheduleParts[2] = string(*sched.DayOfMonth)\n\t}\n\tif sched.Month != nil {\n\t\tscheduleParts[3] = string(*sched.Month)\n\t}\n\tif sched.DayOfWeek != nil {\n\t\tscheduleParts[4] = string(*sched.DayOfWeek)\n\t}\n\tdst.Spec.Schedule = strings.Join(scheduleParts, \" \")\n\n\t/*\n\t\tThe rest of the conversion is pretty rote.\n\t*/\n\t// ObjectMeta\n\tdst.ObjectMeta = src.ObjectMeta\n\n\t// Spec\n\tdst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds\n\tdst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy)\n\tdst.Spec.Suspend = src.Spec.Suspend\n\tdst.Spec.JobTemplate = src.Spec.JobTemplate\n\tdst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit\n\tdst.Spec.FailedJobsHistoryLimit = src.Spec.FailedJobsHistoryLimit\n\n\t// Status\n\tdst.Status.Active = src.Status.Active\n\tdst.Status.LastScheduleTime = src.Status.LastScheduleTime\n\n\treturn nil\n}\n\n// +kubebuilder:docs-gen:collapse=rote conversion\n\n/*\nConvertFrom is expected to modify its receiver to contain the converted object.\nMost of the conversion is straightforward copying, except for converting our changed field.\n*/\n\n// ConvertFrom converts the Hub version (v1) to this CronJob (v2).\nfunc (dst *CronJob) ConvertFrom(srcRaw conversion.Hub) error {\n\tsrc := srcRaw.(*batchv1.CronJob)\n\tlog.Printf(\"ConvertFrom: Converting CronJob from Hub version v1 to Spoke version v2;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\tschedParts := strings.Split(src.Spec.Schedule, \" \")\n\tif len(schedParts) != 5 {\n\t\treturn fmt.Errorf(\"invalid schedule: not a standard 5-field schedule\")\n\t}\n\tpartIfNeeded := func(raw string) *CronField {\n\t\tif raw == \"*\" {\n\t\t\treturn nil\n\t\t}\n\t\tpart := CronField(raw)\n\t\treturn &part\n\t}\n\tdst.Spec.Schedule.Minute = partIfNeeded(schedParts[0])\n\tdst.Spec.Schedule.Hour = partIfNeeded(schedParts[1])\n\tdst.Spec.Schedule.DayOfMonth = partIfNeeded(schedParts[2])\n\tdst.Spec.Schedule.Month = partIfNeeded(schedParts[3])\n\tdst.Spec.Schedule.DayOfWeek = partIfNeeded(schedParts[4])\n\n\t/*\n\t\tThe rest of the conversion is pretty rote.\n\t*/\n\t// ObjectMeta\n\tdst.ObjectMeta = src.ObjectMeta\n\n\t// Spec\n\tdst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds\n\tdst.Spec.ConcurrencyPolicy = ConcurrencyPolicy(src.Spec.ConcurrencyPolicy)\n\tdst.Spec.Suspend = src.Spec.Suspend\n\tdst.Spec.JobTemplate = src.Spec.JobTemplate\n\tdst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit\n\tdst.Spec.FailedJobsHistoryLimit = src.Spec.FailedJobsHistoryLimit\n\n\t// Status\n\tdst.Status.Active = src.Status.Active\n\tdst.Status.LastScheduleTime = src.Status.LastScheduleTime\n\n\treturn nil\n}\n\n// +kubebuilder:docs-gen:collapse=rote conversion\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v2/cronjob_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nSince we're in a v2 package, controller-gen will assume this is for the v2\nversion automatically.  We could override that with the [`+versionName`\nmarker](/reference/markers/crd.md).\n*/\n\npackage v2\n\n/*\n */\nimport (\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nWe'll leave our spec largely unchanged, except to change the schedule field to a new type.\n*/\n// CronJobSpec defines the desired state of CronJob\ntype CronJobSpec struct {\n\t// schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.\n\t// +required\n\tSchedule CronSchedule `json:\"schedule\"`\n\n\t/*\n\t */\n\n\t// startingDeadlineSeconds defines in seconds for starting the job if it misses scheduled\n\t// time for any reason.  Missed jobs executions will be counted as failed ones.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tStartingDeadlineSeconds *int64 `json:\"startingDeadlineSeconds,omitempty\"`\n\n\t// concurrencyPolicy defines how to treat concurrent executions of a Job.\n\t// Valid values are:\n\t// - \"Allow\" (default): allows CronJobs to run concurrently;\n\t// - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet;\n\t// - \"Replace\": cancels currently running job and replaces it with a new one\n\t// +optional\n\t// +kubebuilder:default:=Allow\n\tConcurrencyPolicy ConcurrencyPolicy `json:\"concurrencyPolicy,omitempty\"`\n\n\t// suspend tells the controller to suspend subsequent executions, it does\n\t// not apply to already started executions.  Defaults to false.\n\t// +optional\n\tSuspend *bool `json:\"suspend,omitempty\"`\n\n\t// jobTemplate defines the job that will be created when executing a CronJob.\n\t// +required\n\tJobTemplate batchv1.JobTemplateSpec `json:\"jobTemplate\"`\n\n\t// successfulJobsHistoryLimit defines the number of successful finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tSuccessfulJobsHistoryLimit *int32 `json:\"successfulJobsHistoryLimit,omitempty\"`\n\n\t// failedJobsHistoryLimit defines the number of failed finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tFailedJobsHistoryLimit *int32 `json:\"failedJobsHistoryLimit,omitempty\"`\n}\n\n// +kubebuilder:docs-gen:collapse=CronJobSpec Full Code\n\n/*\nNext, we'll need to define a type to hold our schedule.\nBased on our proposed YAML above, it'll have a field for\neach corresponding Cron \"field\".\n*/\n\n// describes a Cron schedule.\ntype CronSchedule struct {\n\t// minute specifies the minutes during which the job executes.\n\t// +optional\n\tMinute *CronField `json:\"minute,omitempty\"`\n\t// hour specifies the hour during which the job executes.\n\t// +optional\n\tHour *CronField `json:\"hour,omitempty\"`\n\t// dayOfMonth specifies the day of the month during which the job executes.\n\t// +optional\n\tDayOfMonth *CronField `json:\"dayOfMonth,omitempty\"`\n\t// month specifies the month during which the job executes.\n\t// +optional\n\tMonth *CronField `json:\"month,omitempty\"`\n\t// dayOfWeek specifies the day of the week during which the job executes.\n\t// +optional\n\tDayOfWeek *CronField `json:\"dayOfWeek,omitempty\"`\n}\n\n/*\nFinally, we'll define a wrapper type to represent a field.\nWe could attach additional validation to this field,\nbut for now we'll just use it for documentation purposes.\n*/\n\n// represents a Cron field specifier.\ntype CronField string\n\n/*\nAll the other types will stay the same as before.\n*/\n\n// ConcurrencyPolicy describes how the job will be handled.\n// Only one of the following concurrent policies may be specified.\n// If none of the following policies is specified, the default one\n// is AllowConcurrent.\n// +kubebuilder:validation:Enum=Allow;Forbid;Replace\ntype ConcurrencyPolicy string\n\nconst (\n\t// AllowConcurrent allows CronJobs to run concurrently.\n\tAllowConcurrent ConcurrencyPolicy = \"Allow\"\n\n\t// ForbidConcurrent forbids concurrent runs, skipping next run if previous\n\t// hasn't finished yet.\n\tForbidConcurrent ConcurrencyPolicy = \"Forbid\"\n\n\t// ReplaceConcurrent cancels currently running job and replaces it with a new one.\n\tReplaceConcurrent ConcurrencyPolicy = \"Replace\"\n)\n\n// CronJobStatus defines the observed state of CronJob.\ntype CronJobStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// active defines a list of pointers to currently running jobs.\n\t// +optional\n\t// +listType=atomic\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:MaxItems=10\n\tActive []corev1.ObjectReference `json:\"active,omitempty\"`\n\n\t// lastScheduleTime defines the information when was the last time the job was successfully scheduled.\n\t// +optional\n\tLastScheduleTime *metav1.Time `json:\"lastScheduleTime,omitempty\"`\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the CronJob resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +versionName=v2\n// CronJob is the Schema for the cronjobs API\ntype CronJob struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of CronJob\n\t// +required\n\tSpec CronJobSpec `json:\"spec\"`\n\n\t// status defines the observed state of CronJob\n\t// +optional\n\tStatus CronJobStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob\ntype CronJobList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []CronJob `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&CronJob{}, &CronJobList{})\n}\n\n// +kubebuilder:docs-gen:collapse=Other Types\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v2/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v2 contains API Schema definitions for the batch v2 API group.\n// +kubebuilder:object:generate=true\n// +groupName=batch.tutorial.kubebuilder.io\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"batch.tutorial.kubebuilder.io\", Version: \"v2\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/api/v2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v2\n\nimport (\n\t\"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJob) DeepCopyInto(out *CronJob) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJob.\nfunc (in *CronJob) DeepCopy() *CronJob {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJob)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJob) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobList) DeepCopyInto(out *CronJobList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]CronJob, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobList.\nfunc (in *CronJobList) DeepCopy() *CronJobList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CronJobList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) {\n\t*out = *in\n\tin.Schedule.DeepCopyInto(&out.Schedule)\n\tif in.StartingDeadlineSeconds != nil {\n\t\tin, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds\n\t\t*out = new(int64)\n\t\t**out = **in\n\t}\n\tif in.Suspend != nil {\n\t\tin, out := &in.Suspend, &out.Suspend\n\t\t*out = new(bool)\n\t\t**out = **in\n\t}\n\tin.JobTemplate.DeepCopyInto(&out.JobTemplate)\n\tif in.SuccessfulJobsHistoryLimit != nil {\n\t\tin, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n\tif in.FailedJobsHistoryLimit != nil {\n\t\tin, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobSpec.\nfunc (in *CronJobSpec) DeepCopy() *CronJobSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronJobStatus) DeepCopyInto(out *CronJobStatus) {\n\t*out = *in\n\tif in.Active != nil {\n\t\tin, out := &in.Active, &out.Active\n\t\t*out = make([]v1.ObjectReference, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.LastScheduleTime != nil {\n\t\tin, out := &in.LastScheduleTime, &out.LastScheduleTime\n\t\t*out = (*in).DeepCopy()\n\t}\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobStatus.\nfunc (in *CronJobStatus) DeepCopy() *CronJobStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronJobStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CronSchedule) DeepCopyInto(out *CronSchedule) {\n\t*out = *in\n\tif in.Minute != nil {\n\t\tin, out := &in.Minute, &out.Minute\n\t\t*out = new(CronField)\n\t\t**out = **in\n\t}\n\tif in.Hour != nil {\n\t\tin, out := &in.Hour, &out.Hour\n\t\t*out = new(CronField)\n\t\t**out = **in\n\t}\n\tif in.DayOfMonth != nil {\n\t\tin, out := &in.DayOfMonth, &out.DayOfMonth\n\t\t*out = new(CronField)\n\t\t**out = **in\n\t}\n\tif in.Month != nil {\n\t\tin, out := &in.Month, &out.Month\n\t\t*out = new(CronField)\n\t\t**out = **in\n\t}\n\tif in.DayOfWeek != nil {\n\t\tin, out := &in.DayOfWeek, &out.DayOfWeek\n\t\t*out = new(CronField)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronSchedule.\nfunc (in *CronSchedule) DeepCopy() *CronSchedule {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CronSchedule)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\tkbatchv1 \"k8s.io/api/batch/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\tbatchv2 \"tutorial.kubebuilder.io/project/api/v2\"\n\t\"tutorial.kubebuilder.io/project/internal/controller\"\n\twebhookv1 \"tutorial.kubebuilder.io/project/internal/webhook/v1\"\n\twebhookv2 \"tutorial.kubebuilder.io/project/internal/webhook/v2\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\n */\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(kbatchv1.AddToScheme(scheme)) // we've added this ourselves\n\tutilruntime.Must(batchv1.AddToScheme(scheme))\n\tutilruntime.Must(batchv2.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n// +kubebuilder:docs-gen:collapse=existing setup\n\n/*\n */\n\n// nolint:gocyclo\nfunc main() {\n\t/*\n\t */\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// +kubebuilder:docs-gen:collapse=Manager Setup\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"80807133.tutorial.kubebuilder.io\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := (&controller.CronJobReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"CronJob\")\n\t\tos.Exit(1)\n\t}\n\n\t/*\n\t\tOur existing call to SetupWebhookWithManager registers our conversion webhooks with the manager, too.\n\t*/\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupCronJobWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"CronJob\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv2.SetupCronJobWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"CronJob\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/certmanager/certificate-metrics.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/certmanager/certificate-webhook.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/certmanager/issuer.yaml",
    "content": "# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/certmanager/kustomization.yaml",
    "content": "resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/crd/bases/batch.tutorial.kubebuilder.io_cronjobs.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                properties:\n                  dayOfMonth:\n                    type: string\n                  dayOfWeek:\n                    type: string\n                  hour:\n                    type: string\n                  minute:\n                    type: string\n                  month:\n                    type: string\n                type: object\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/batch.tutorial.kubebuilder.io_cronjobs.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n- path: patches/webhook_in_cronjobs.yaml\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/crd/patches/webhook_in_cronjobs.yaml",
    "content": "# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# ANCHOR: webhook-resources\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# ANCHOR_END: webhook-resources\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n- path: cert_metrics_manager_patch.yaml\n  target:\n    kind: Deployment\n\n# ANCHOR: webhook-patch\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n# ANCHOR_END: webhook-patch\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\nreplacements:\n - source: # Uncomment the following block to enable certificates for metrics\n     kind: Service\n     version: v1\n     name: controller-manager-metrics-service\n     fieldPath: metadata.name\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: metrics-certs\n       fieldPaths:\n         - spec.dnsNames.0\n         - spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n         kind: ServiceMonitor\n         group: monitoring.coreos.com\n         version: v1\n         name: controller-manager-metrics-monitor\n       fieldPaths:\n         - spec.endpoints.0.tlsConfig.serverName\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n\n - source:\n     kind: Service\n     version: v1\n     name: controller-manager-metrics-service\n     fieldPath: metadata.namespace\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: metrics-certs\n       fieldPaths:\n         - spec.dnsNames.0\n         - spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n         kind: ServiceMonitor\n         group: monitoring.coreos.com\n         version: v1\n         name: controller-manager-metrics-monitor\n       fieldPaths:\n         - spec.endpoints.0.tlsConfig.serverName\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n # ANCHOR: webhook-replacements\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n - source:\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.namespace # Namespace of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert # This name should match the one in certificate.yaml\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# ANCHOR_END: webhook-replacements\n\n - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: cronjobs.batch.tutorial.kubebuilder.io\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: cronjobs.batch.tutorial.kubebuilder.io\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_webhook_patch.yaml",
    "content": "# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/allow-webhook-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-webhook-traffic.yaml\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\npatches:\n  - path: monitor_tls_patch.yaml\n    target:\n      kind: ServiceMonitor\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/cronjob_admin_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over batch.tutorial.kubebuilder.io.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-admin-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/cronjob_editor_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the batch.tutorial.kubebuilder.io.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-editor-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/cronjob_viewer_role.yaml",
    "content": "# This rule is not used by the project project itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to batch.tutorial.kubebuilder.io resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-viewer-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- cronjob_admin_role.yaml\n- cronjob_editor_role.yaml\n- cronjob_viewer_role.yaml\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/samples/batch_v1_cronjob.yaml",
    "content": "apiVersion: batch.tutorial.kubebuilder.io/v1\nkind: CronJob\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-sample\nspec:\n  schedule: \"*/1 * * * *\"\n  startingDeadlineSeconds: 60\n  concurrencyPolicy: Allow # explicitly specify, but Allow is also default.\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          securityContext:\n            runAsNonRoot: true\n            runAsUser: 1000\n            seccompProfile:\n              type: RuntimeDefault\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            securityContext:\n              allowPrivilegeEscalation: false\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: false\n          restartPolicy: OnFailure\n  \n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/samples/batch_v2_cronjob.yaml",
    "content": "apiVersion: batch.tutorial.kubebuilder.io/v2\nkind: CronJob\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-sample\nspec:\n  schedule:\n    minute: \"*/1\"\n  startingDeadlineSeconds: 60\n  concurrencyPolicy: Allow # explicitly specify, but Allow is also default.\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          securityContext:\n            runAsNonRoot: true\n            runAsUser: 1000\n            seccompProfile:\n              type: RuntimeDefault\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            securityContext:\n              allowPrivilegeEscalation: false\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: false\n          restartPolicy: OnFailure\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- batch_v1_cronjob.yaml\n- batch_v2_cronjob.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/webhook/manifests.yaml",
    "content": "---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/config/webhook/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: project\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/.helmignore",
    "content": "# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/Chart.yaml",
    "content": "apiVersion: v2\nname: project\ndescription: A Helm chart to distribute project\ntype: application\n\nversion: 0.1.0\nappVersion: \"0.1.0\"\n\nkeywords:\n  - kubernetes\n  - operator\n\nannotations:\n  kubebuilder.io/generated-by: kubebuilder\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/NOTES.txt",
    "content": "Thank you for installing {{ .Chart.Name }}.\n\nYour release is named {{ .Release.Name }}.\n\nThe controller and CRDs have been installed in namespace {{ .Release.Namespace }}.\n\nTo verify the installation:\n\n  kubectl get pods -n {{ .Release.Namespace }}\n  kubectl get customresourcedefinitions\n\nTo learn more about the release, try:\n\n  $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}\n  $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"project.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"project.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nNamespace for generated references.\nAlways uses the Helm release namespace.\n*/}}\n{{- define \"project.namespaceName\" -}}\n{{- .Release.Namespace }}\n{{- end }}\n\n{{/*\nResource name with proper truncation for Kubernetes 63-character limit.\nTakes a dict with:\n  - .suffix: Resource name suffix (e.g., \"metrics\", \"webhook\")\n  - .context: Template context (root context with .Values, .Release, etc.)\nDynamically calculates safe truncation to ensure total name length <= 63 chars.\n*/}}\n{{- define \"project.resourceName\" -}}\n{{- $fullname := include \"project.fullname\" .context }}\n{{- $suffix := .suffix }}\n{{- $maxLen := sub 62 (len $suffix) | int }}\n{{- if gt (len $fullname) $maxLen }}\n{{- printf \"%s-%s\" (trunc $maxLen $fullname | trimSuffix \"-\") $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" $fullname $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/cert-manager/metrics-certs.yaml",
    "content": "{{- if and .Values.certManager.enable .Values.metrics.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-certs\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: metrics-server-cert\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/cert-manager/selfsigned-issuer.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selfSigned: {}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/cert-manager/serving-cert.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  - {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: webhook-server-cert\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/crd/cronjobs.batch.tutorial.kubebuilder.io.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n          namespace: {{ .Release.Namespace }}\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                properties:\n                  dayOfMonth:\n                    type: string\n                  dayOfWeek:\n                    type: string\n                  hour:\n                    type: string\n                  minute:\n                    type: string\n                  month:\n                    type: string\n                type: object\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/manager/manager.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.manager.replicas }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: {{ include \"project.name\" . }}\n        helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n        app.kubernetes.io/managed-by: {{ .Release.Service }}\n        control-plane: controller-manager\n    spec:\n      {{- with .Values.manager.tolerations }}\n      tolerations: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.affinity }}\n      affinity: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      containers:\n      - args:\n        {{- if .Values.metrics.enable }}\n        - --metrics-bind-address=:{{ .Values.metrics.port }}\n        {{- else }}\n        # Bind to :0 to disable the controller-runtime managed metrics server\n        - --metrics-bind-address=0\n        {{- end }}\n        - --health-probe-bind-address=:8081\n        {{- range .Values.manager.args }}\n        - {{ . }}\n        {{- end }}\n        {{- if and .Values.certManager.enable .Values.metrics.enable }}\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n        {{- end }}\n        {{- if .Values.certManager.enable }}\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        {{- end }}\n        command:\n        - /manager\n        image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"\n        imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: {{ .Values.webhook.port }}\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          {{- if .Values.manager.resources }}\n          {{- toYaml .Values.manager.resources | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        securityContext:\n          {{- if .Values.manager.securityContext }}\n          {{- toYaml .Values.manager.securityContext | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        volumeMounts:\n        {{- if and .Values.certManager.enable .Values.metrics.enable }}\n        - mountPath: /tmp/k8s-metrics-server/metrics-certs\n          name: metrics-certs\n          readOnly: true\n        {{- end }}\n        {{- if .Values.certManager.enable }}\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n        {{- end }}\n      securityContext:\n        {{- if .Values.manager.podSecurityContext }}\n        {{- toYaml .Values.manager.podSecurityContext | nindent 8 }}\n        {{- else }}\n        {}\n        {{- end }}\n      serviceAccountName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n      terminationGracePeriodSeconds: 10\n      volumes:\n      {{- if and .Values.certManager.enable .Values.metrics.enable }}\n      - name: metrics-certs\n        secret:\n          items:\n          - key: ca.crt\n            path: ca.crt\n          - key: tls.crt\n            path: tls.crt\n          - key: tls.key\n            path: tls.key\n          optional: false\n          secretName: metrics-server-cert\n      {{- end }}\n      {{- if .Values.certManager.enable }}\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n      {{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/metrics/controller-manager-metrics-service.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: https\n    port: {{ .Values.metrics.port }}\n    protocol: TCP\n    targetPort: {{ .Values.metrics.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/prometheus/controller-manager-metrics-monitor.yaml",
    "content": "{{- if .Values.prometheus.enable }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-monitor\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  endpoints:\n  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n    path: /metrics\n    port: https\n    scheme: https\n    tlsConfig:\n      ca:\n        secret:\n          key: ca.crt\n          name: metrics-server-cert\n      cert:\n        secret:\n          key: tls.crt\n          name: metrics-server-cert\n      insecureSkipVerify: false\n      keySecret:\n        key: tls.key\n        name: metrics-server-cert\n      serverName: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project.name\" . }}\n      control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/controller-manager.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-admin-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-editor-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/cronjob-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"cronjob-viewer-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/leader-election-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/leader-election-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-rolebinding\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/manager-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/metrics-auth-role.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/metrics-auth-rolebinding.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/rbac/metrics-reader.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"metrics-reader\" \"context\" $) }}\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/webhook/mutating-webhook-configuration.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    {{- if .Values.certManager.enable }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    {{- end }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"mutating-webhook-configuration\" \"context\" $) }}\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /mutate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/webhook/validating-webhook-configuration.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    {{- if .Values.certManager.enable }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    {{- end }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"validating-webhook-configuration\" \"context\" $) }}\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /validate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/templates/webhook/webhook-service.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: {{ .Values.webhook.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/chart/values.yaml",
    "content": "## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: controller\n    tag: latest\n    pullPolicy: IfNotPresent\n\n  ## Arguments\n  ##\n  args:\n    - --leader-elect\n\n  ## Environment variables\n  ##\n  env: []\n\n  ## Env overrides (--set manager.envOverrides.VAR=value)\n  ## Same name in env above: this value takes precedence.\n  ##\n  envOverrides: {}\n\n  ## Image pull secrets\n  ##\n  imagePullSecrets: []\n\n  ## Pod-level security settings\n  ##\n  podSecurityContext:\n    runAsNonRoot: true\n    seccompProfile:\n      type: RuntimeDefault\n\n  ## Container-level security settings\n  ##\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n      drop:\n      - ALL\n    readOnlyRootFilesystem: true\n\n  ## Resource limits and requests\n  ##\n  resources:\n    limits:\n      cpu: 500m\n      memory: 128Mi\n    requests:\n      cpu: 10m\n      memory: 64Mi\n\n  ## Manager pod's affinity\n  ##\n  affinity: {}\n\n  ## Manager pod's node selector\n  ##\n  nodeSelector: {}\n\n  ## Manager pod's tolerations\n  ##\n  tolerations: []\n\n## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: 8443\n\n## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: true\n\n## Webhook server configuration\n##\nwebhook:\n  enable: true\n  # Webhook server port\n  port: 9443\n\n## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-system/project-serving-cert\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cronjobs.batch.tutorial.kubebuilder.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: project-webhook-service\n          namespace: project-system\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                minLength: 0\n                type: string\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        properties:\n          apiVersion:\n            type: string\n          kind:\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              concurrencyPolicy:\n                default: Allow\n                enum:\n                - Allow\n                - Forbid\n                - Replace\n                type: string\n              failedJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              jobTemplate:\n                properties:\n                  metadata:\n                    type: object\n                  spec:\n                    properties:\n                      activeDeadlineSeconds:\n                        format: int64\n                        type: integer\n                      backoffLimit:\n                        format: int32\n                        type: integer\n                      backoffLimitPerIndex:\n                        format: int32\n                        type: integer\n                      completionMode:\n                        type: string\n                      completions:\n                        format: int32\n                        type: integer\n                      managedBy:\n                        type: string\n                      manualSelector:\n                        type: boolean\n                      maxFailedIndexes:\n                        format: int32\n                        type: integer\n                      parallelism:\n                        format: int32\n                        type: integer\n                      podFailurePolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                action:\n                                  type: string\n                                onExitCodes:\n                                  properties:\n                                    containerName:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    values:\n                                      items:\n                                        format: int32\n                                        type: integer\n                                      type: array\n                                      x-kubernetes-list-type: set\n                                  required:\n                                  - operator\n                                  - values\n                                  type: object\n                                onPodConditions:\n                                  items:\n                                    properties:\n                                      status:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - action\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      podReplacementPolicy:\n                        type: string\n                      selector:\n                        properties:\n                          matchExpressions:\n                            items:\n                              properties:\n                                key:\n                                  type: string\n                                operator:\n                                  type: string\n                                values:\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                      successPolicy:\n                        properties:\n                          rules:\n                            items:\n                              properties:\n                                succeededCount:\n                                  format: int32\n                                  type: integer\n                                succeededIndexes:\n                                  type: string\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        required:\n                        - rules\n                        type: object\n                      suspend:\n                        type: boolean\n                      template:\n                        properties:\n                          metadata:\n                            type: object\n                          spec:\n                            properties:\n                              activeDeadlineSeconds:\n                                format: int64\n                                type: integer\n                              affinity:\n                                properties:\n                                  nodeAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            preference:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - preference\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        properties:\n                                          nodeSelectorTerms:\n                                            items:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchFields:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - nodeSelectorTerms\n                                        type: object\n                                        x-kubernetes-map-type: atomic\n                                    type: object\n                                  podAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                  podAntiAffinity:\n                                    properties:\n                                      preferredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            podAffinityTerm:\n                                              properties:\n                                                labelSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                matchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                mismatchLabelKeys:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                namespaceSelector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                namespaces:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                topologyKey:\n                                                  type: string\n                                              required:\n                                              - topologyKey\n                                              type: object\n                                            weight:\n                                              format: int32\n                                              type: integer\n                                          required:\n                                          - podAffinityTerm\n                                          - weight\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                      requiredDuringSchedulingIgnoredDuringExecution:\n                                        items:\n                                          properties:\n                                            labelSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            matchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            mismatchLabelKeys:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            namespaceSelector:\n                                              properties:\n                                                matchExpressions:\n                                                  items:\n                                                    properties:\n                                                      key:\n                                                        type: string\n                                                      operator:\n                                                        type: string\n                                                      values:\n                                                        items:\n                                                          type: string\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                    required:\n                                                    - key\n                                                    - operator\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                matchLabels:\n                                                  additionalProperties:\n                                                    type: string\n                                                  type: object\n                                              type: object\n                                              x-kubernetes-map-type: atomic\n                                            namespaces:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            topologyKey:\n                                              type: string\n                                          required:\n                                          - topologyKey\n                                          type: object\n                                        type: array\n                                        x-kubernetes-list-type: atomic\n                                    type: object\n                                type: object\n                              automountServiceAccountToken:\n                                type: boolean\n                              containers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              dnsConfig:\n                                properties:\n                                  nameservers:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  options:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  searches:\n                                    items:\n                                      type: string\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                type: object\n                              dnsPolicy:\n                                type: string\n                              enableServiceLinks:\n                                type: boolean\n                              ephemeralContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    targetContainerName:\n                                      type: string\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              hostAliases:\n                                items:\n                                  properties:\n                                    hostnames:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    ip:\n                                      type: string\n                                  required:\n                                  - ip\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - ip\n                                x-kubernetes-list-type: map\n                              hostIPC:\n                                type: boolean\n                              hostNetwork:\n                                type: boolean\n                              hostPID:\n                                type: boolean\n                              hostUsers:\n                                type: boolean\n                              hostname:\n                                type: string\n                              hostnameOverride:\n                                type: string\n                              imagePullSecrets:\n                                items:\n                                  properties:\n                                    name:\n                                      default: \"\"\n                                      type: string\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              initContainers:\n                                items:\n                                  properties:\n                                    args:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    command:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    env:\n                                      items:\n                                        properties:\n                                          name:\n                                            type: string\n                                          value:\n                                            type: string\n                                          valueFrom:\n                                            properties:\n                                              configMapKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              fileKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  optional:\n                                                    default: false\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  volumeName:\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                - volumeName\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              secretKeyRef:\n                                                properties:\n                                                  key:\n                                                    type: string\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                required:\n                                                - key\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            type: object\n                                        required:\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - name\n                                      x-kubernetes-list-type: map\n                                    envFrom:\n                                      items:\n                                        properties:\n                                          configMapRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                          prefix:\n                                            type: string\n                                          secretRef:\n                                            properties:\n                                              name:\n                                                default: \"\"\n                                                type: string\n                                              optional:\n                                                type: boolean\n                                            type: object\n                                            x-kubernetes-map-type: atomic\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    image:\n                                      type: string\n                                    imagePullPolicy:\n                                      type: string\n                                    lifecycle:\n                                      properties:\n                                        postStart:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        preStop:\n                                          properties:\n                                            exec:\n                                              properties:\n                                                command:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                              type: object\n                                            httpGet:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                httpHeaders:\n                                                  items:\n                                                    properties:\n                                                      name:\n                                                        type: string\n                                                      value:\n                                                        type: string\n                                                    required:\n                                                    - name\n                                                    - value\n                                                    type: object\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                path:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                                scheme:\n                                                  type: string\n                                              required:\n                                              - port\n                                              type: object\n                                            sleep:\n                                              properties:\n                                                seconds:\n                                                  format: int64\n                                                  type: integer\n                                              required:\n                                              - seconds\n                                              type: object\n                                            tcpSocket:\n                                              properties:\n                                                host:\n                                                  type: string\n                                                port:\n                                                  anyOf:\n                                                  - type: integer\n                                                  - type: string\n                                                  x-kubernetes-int-or-string: true\n                                              required:\n                                              - port\n                                              type: object\n                                          type: object\n                                        stopSignal:\n                                          type: string\n                                      type: object\n                                    livenessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    name:\n                                      type: string\n                                    ports:\n                                      items:\n                                        properties:\n                                          containerPort:\n                                            format: int32\n                                            type: integer\n                                          hostIP:\n                                            type: string\n                                          hostPort:\n                                            format: int32\n                                            type: integer\n                                          name:\n                                            type: string\n                                          protocol:\n                                            default: TCP\n                                            type: string\n                                        required:\n                                        - containerPort\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - containerPort\n                                      - protocol\n                                      x-kubernetes-list-type: map\n                                    readinessProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    resizePolicy:\n                                      items:\n                                        properties:\n                                          resourceName:\n                                            type: string\n                                          restartPolicy:\n                                            type: string\n                                        required:\n                                        - resourceName\n                                        - restartPolicy\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    resources:\n                                      properties:\n                                        claims:\n                                          items:\n                                            properties:\n                                              name:\n                                                type: string\n                                              request:\n                                                type: string\n                                            required:\n                                            - name\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-map-keys:\n                                          - name\n                                          x-kubernetes-list-type: map\n                                        limits:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                        requests:\n                                          additionalProperties:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                            x-kubernetes-int-or-string: true\n                                          type: object\n                                      type: object\n                                    restartPolicy:\n                                      type: string\n                                    restartPolicyRules:\n                                      items:\n                                        properties:\n                                          action:\n                                            type: string\n                                          exitCodes:\n                                            properties:\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  format: int32\n                                                  type: integer\n                                                type: array\n                                                x-kubernetes-list-type: set\n                                            required:\n                                            - operator\n                                            type: object\n                                        required:\n                                        - action\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    securityContext:\n                                      properties:\n                                        allowPrivilegeEscalation:\n                                          type: boolean\n                                        appArmorProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        capabilities:\n                                          properties:\n                                            add:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            drop:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        privileged:\n                                          type: boolean\n                                        procMount:\n                                          type: string\n                                        readOnlyRootFilesystem:\n                                          type: boolean\n                                        runAsGroup:\n                                          format: int64\n                                          type: integer\n                                        runAsNonRoot:\n                                          type: boolean\n                                        runAsUser:\n                                          format: int64\n                                          type: integer\n                                        seLinuxOptions:\n                                          properties:\n                                            level:\n                                              type: string\n                                            role:\n                                              type: string\n                                            type:\n                                              type: string\n                                            user:\n                                              type: string\n                                          type: object\n                                        seccompProfile:\n                                          properties:\n                                            localhostProfile:\n                                              type: string\n                                            type:\n                                              type: string\n                                          required:\n                                          - type\n                                          type: object\n                                        windowsOptions:\n                                          properties:\n                                            gmsaCredentialSpec:\n                                              type: string\n                                            gmsaCredentialSpecName:\n                                              type: string\n                                            hostProcess:\n                                              type: boolean\n                                            runAsUserName:\n                                              type: string\n                                          type: object\n                                      type: object\n                                    startupProbe:\n                                      properties:\n                                        exec:\n                                          properties:\n                                            command:\n                                              items:\n                                                type: string\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                          type: object\n                                        failureThreshold:\n                                          format: int32\n                                          type: integer\n                                        grpc:\n                                          properties:\n                                            port:\n                                              format: int32\n                                              type: integer\n                                            service:\n                                              default: \"\"\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        httpGet:\n                                          properties:\n                                            host:\n                                              type: string\n                                            httpHeaders:\n                                              items:\n                                                properties:\n                                                  name:\n                                                    type: string\n                                                  value:\n                                                    type: string\n                                                required:\n                                                - name\n                                                - value\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            path:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                            scheme:\n                                              type: string\n                                          required:\n                                          - port\n                                          type: object\n                                        initialDelaySeconds:\n                                          format: int32\n                                          type: integer\n                                        periodSeconds:\n                                          format: int32\n                                          type: integer\n                                        successThreshold:\n                                          format: int32\n                                          type: integer\n                                        tcpSocket:\n                                          properties:\n                                            host:\n                                              type: string\n                                            port:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              x-kubernetes-int-or-string: true\n                                          required:\n                                          - port\n                                          type: object\n                                        terminationGracePeriodSeconds:\n                                          format: int64\n                                          type: integer\n                                        timeoutSeconds:\n                                          format: int32\n                                          type: integer\n                                      type: object\n                                    stdin:\n                                      type: boolean\n                                    stdinOnce:\n                                      type: boolean\n                                    terminationMessagePath:\n                                      type: string\n                                    terminationMessagePolicy:\n                                      type: string\n                                    tty:\n                                      type: boolean\n                                    volumeDevices:\n                                      items:\n                                        properties:\n                                          devicePath:\n                                            type: string\n                                          name:\n                                            type: string\n                                        required:\n                                        - devicePath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - devicePath\n                                      x-kubernetes-list-type: map\n                                    volumeMounts:\n                                      items:\n                                        properties:\n                                          mountPath:\n                                            type: string\n                                          mountPropagation:\n                                            type: string\n                                          name:\n                                            type: string\n                                          readOnly:\n                                            type: boolean\n                                          recursiveReadOnly:\n                                            type: string\n                                          subPath:\n                                            type: string\n                                          subPathExpr:\n                                            type: string\n                                        required:\n                                        - mountPath\n                                        - name\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-map-keys:\n                                      - mountPath\n                                      x-kubernetes-list-type: map\n                                    workingDir:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              nodeName:\n                                type: string\n                              nodeSelector:\n                                additionalProperties:\n                                  type: string\n                                type: object\n                                x-kubernetes-map-type: atomic\n                              os:\n                                properties:\n                                  name:\n                                    type: string\n                                required:\n                                - name\n                                type: object\n                              overhead:\n                                additionalProperties:\n                                  anyOf:\n                                  - type: integer\n                                  - type: string\n                                  pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                  x-kubernetes-int-or-string: true\n                                type: object\n                              preemptionPolicy:\n                                type: string\n                              priority:\n                                format: int32\n                                type: integer\n                              priorityClassName:\n                                type: string\n                              readinessGates:\n                                items:\n                                  properties:\n                                    conditionType:\n                                      type: string\n                                  required:\n                                  - conditionType\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              resourceClaims:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                    resourceClaimName:\n                                      type: string\n                                    resourceClaimTemplateName:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              resources:\n                                properties:\n                                  claims:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        request:\n                                          type: string\n                                      required:\n                                      - name\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-map-keys:\n                                    - name\n                                    x-kubernetes-list-type: map\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    type: object\n                                type: object\n                              restartPolicy:\n                                type: string\n                              runtimeClassName:\n                                type: string\n                              schedulerName:\n                                type: string\n                              schedulingGates:\n                                items:\n                                  properties:\n                                    name:\n                                      type: string\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              securityContext:\n                                properties:\n                                  appArmorProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  fsGroup:\n                                    format: int64\n                                    type: integer\n                                  fsGroupChangePolicy:\n                                    type: string\n                                  runAsGroup:\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    type: boolean\n                                  runAsUser:\n                                    format: int64\n                                    type: integer\n                                  seLinuxChangePolicy:\n                                    type: string\n                                  seLinuxOptions:\n                                    properties:\n                                      level:\n                                        type: string\n                                      role:\n                                        type: string\n                                      type:\n                                        type: string\n                                      user:\n                                        type: string\n                                    type: object\n                                  seccompProfile:\n                                    properties:\n                                      localhostProfile:\n                                        type: string\n                                      type:\n                                        type: string\n                                    required:\n                                    - type\n                                    type: object\n                                  supplementalGroups:\n                                    items:\n                                      format: int64\n                                      type: integer\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  supplementalGroupsPolicy:\n                                    type: string\n                                  sysctls:\n                                    items:\n                                      properties:\n                                        name:\n                                          type: string\n                                        value:\n                                          type: string\n                                      required:\n                                      - name\n                                      - value\n                                      type: object\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                  windowsOptions:\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        type: string\n                                      hostProcess:\n                                        type: boolean\n                                      runAsUserName:\n                                        type: string\n                                    type: object\n                                type: object\n                              serviceAccount:\n                                type: string\n                              serviceAccountName:\n                                type: string\n                              setHostnameAsFQDN:\n                                type: boolean\n                              shareProcessNamespace:\n                                type: boolean\n                              subdomain:\n                                type: string\n                              terminationGracePeriodSeconds:\n                                format: int64\n                                type: integer\n                              tolerations:\n                                items:\n                                  properties:\n                                    effect:\n                                      type: string\n                                    key:\n                                      type: string\n                                    operator:\n                                      type: string\n                                    tolerationSeconds:\n                                      format: int64\n                                      type: integer\n                                    value:\n                                      type: string\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              topologySpreadConstraints:\n                                items:\n                                  properties:\n                                    labelSelector:\n                                      properties:\n                                        matchExpressions:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              operator:\n                                                type: string\n                                              values:\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    maxSkew:\n                                      format: int32\n                                      type: integer\n                                    minDomains:\n                                      format: int32\n                                      type: integer\n                                    nodeAffinityPolicy:\n                                      type: string\n                                    nodeTaintsPolicy:\n                                      type: string\n                                    topologyKey:\n                                      type: string\n                                    whenUnsatisfiable:\n                                      type: string\n                                  required:\n                                  - maxSkew\n                                  - topologyKey\n                                  - whenUnsatisfiable\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - topologyKey\n                                - whenUnsatisfiable\n                                x-kubernetes-list-type: map\n                              volumes:\n                                items:\n                                  properties:\n                                    awsElasticBlockStore:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    azureDisk:\n                                      properties:\n                                        cachingMode:\n                                          type: string\n                                        diskName:\n                                          type: string\n                                        diskURI:\n                                          type: string\n                                        fsType:\n                                          default: ext4\n                                          type: string\n                                        kind:\n                                          type: string\n                                        readOnly:\n                                          default: false\n                                          type: boolean\n                                      required:\n                                      - diskName\n                                      - diskURI\n                                      type: object\n                                    azureFile:\n                                      properties:\n                                        readOnly:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                        shareName:\n                                          type: string\n                                      required:\n                                      - secretName\n                                      - shareName\n                                      type: object\n                                    cephfs:\n                                      properties:\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretFile:\n                                          type: string\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          type: string\n                                      required:\n                                      - monitors\n                                      type: object\n                                    cinder:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    configMap:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        name:\n                                          default: \"\"\n                                          type: string\n                                        optional:\n                                          type: boolean\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    csi:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        nodePublishSecretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        volumeAttributes:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                      required:\n                                      - driver\n                                      type: object\n                                    downwardAPI:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              fieldRef:\n                                                properties:\n                                                  apiVersion:\n                                                    type: string\n                                                  fieldPath:\n                                                    type: string\n                                                required:\n                                                - fieldPath\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                              resourceFieldRef:\n                                                properties:\n                                                  containerName:\n                                                    type: string\n                                                  divisor:\n                                                    anyOf:\n                                                    - type: integer\n                                                    - type: string\n                                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                    x-kubernetes-int-or-string: true\n                                                  resource:\n                                                    type: string\n                                                required:\n                                                - resource\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                            required:\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    emptyDir:\n                                      properties:\n                                        medium:\n                                          type: string\n                                        sizeLimit:\n                                          anyOf:\n                                          - type: integer\n                                          - type: string\n                                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                          x-kubernetes-int-or-string: true\n                                      type: object\n                                    ephemeral:\n                                      properties:\n                                        volumeClaimTemplate:\n                                          properties:\n                                            metadata:\n                                              type: object\n                                            spec:\n                                              properties:\n                                                accessModes:\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                                  x-kubernetes-list-type: atomic\n                                                dataSource:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                dataSourceRef:\n                                                  properties:\n                                                    apiGroup:\n                                                      type: string\n                                                    kind:\n                                                      type: string\n                                                    name:\n                                                      type: string\n                                                    namespace:\n                                                      type: string\n                                                  required:\n                                                  - kind\n                                                  - name\n                                                  type: object\n                                                resources:\n                                                  properties:\n                                                    limits:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                    requests:\n                                                      additionalProperties:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      type: object\n                                                  type: object\n                                                selector:\n                                                  properties:\n                                                    matchExpressions:\n                                                      items:\n                                                        properties:\n                                                          key:\n                                                            type: string\n                                                          operator:\n                                                            type: string\n                                                          values:\n                                                            items:\n                                                              type: string\n                                                            type: array\n                                                            x-kubernetes-list-type: atomic\n                                                        required:\n                                                        - key\n                                                        - operator\n                                                        type: object\n                                                      type: array\n                                                      x-kubernetes-list-type: atomic\n                                                    matchLabels:\n                                                      additionalProperties:\n                                                        type: string\n                                                      type: object\n                                                  type: object\n                                                  x-kubernetes-map-type: atomic\n                                                storageClassName:\n                                                  type: string\n                                                volumeAttributesClassName:\n                                                  type: string\n                                                volumeMode:\n                                                  type: string\n                                                volumeName:\n                                                  type: string\n                                              type: object\n                                          required:\n                                          - spec\n                                          type: object\n                                      type: object\n                                    fc:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        readOnly:\n                                          type: boolean\n                                        targetWWNs:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        wwids:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    flexVolume:\n                                      properties:\n                                        driver:\n                                          type: string\n                                        fsType:\n                                          type: string\n                                        options:\n                                          additionalProperties:\n                                            type: string\n                                          type: object\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                      required:\n                                      - driver\n                                      type: object\n                                    flocker:\n                                      properties:\n                                        datasetName:\n                                          type: string\n                                        datasetUUID:\n                                          type: string\n                                      type: object\n                                    gcePersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        partition:\n                                          format: int32\n                                          type: integer\n                                        pdName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - pdName\n                                      type: object\n                                    gitRepo:\n                                      properties:\n                                        directory:\n                                          type: string\n                                        repository:\n                                          type: string\n                                        revision:\n                                          type: string\n                                      required:\n                                      - repository\n                                      type: object\n                                    glusterfs:\n                                      properties:\n                                        endpoints:\n                                          type: string\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - endpoints\n                                      - path\n                                      type: object\n                                    hostPath:\n                                      properties:\n                                        path:\n                                          type: string\n                                        type:\n                                          type: string\n                                      required:\n                                      - path\n                                      type: object\n                                    image:\n                                      properties:\n                                        pullPolicy:\n                                          type: string\n                                        reference:\n                                          type: string\n                                      type: object\n                                    iscsi:\n                                      properties:\n                                        chapAuthDiscovery:\n                                          type: boolean\n                                        chapAuthSession:\n                                          type: boolean\n                                        fsType:\n                                          type: string\n                                        initiatorName:\n                                          type: string\n                                        iqn:\n                                          type: string\n                                        iscsiInterface:\n                                          default: default\n                                          type: string\n                                        lun:\n                                          format: int32\n                                          type: integer\n                                        portals:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        targetPortal:\n                                          type: string\n                                      required:\n                                      - iqn\n                                      - lun\n                                      - targetPortal\n                                      type: object\n                                    name:\n                                      type: string\n                                    nfs:\n                                      properties:\n                                        path:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        server:\n                                          type: string\n                                      required:\n                                      - path\n                                      - server\n                                      type: object\n                                    persistentVolumeClaim:\n                                      properties:\n                                        claimName:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                      required:\n                                      - claimName\n                                      type: object\n                                    photonPersistentDisk:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        pdID:\n                                          type: string\n                                      required:\n                                      - pdID\n                                      type: object\n                                    portworxVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        volumeID:\n                                          type: string\n                                      required:\n                                      - volumeID\n                                      type: object\n                                    projected:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        sources:\n                                          items:\n                                            properties:\n                                              clusterTrustBundle:\n                                                properties:\n                                                  labelSelector:\n                                                    properties:\n                                                      matchExpressions:\n                                                        items:\n                                                          properties:\n                                                            key:\n                                                              type: string\n                                                            operator:\n                                                              type: string\n                                                            values:\n                                                              items:\n                                                                type: string\n                                                              type: array\n                                                              x-kubernetes-list-type: atomic\n                                                          required:\n                                                          - key\n                                                          - operator\n                                                          type: object\n                                                        type: array\n                                                        x-kubernetes-list-type: atomic\n                                                      matchLabels:\n                                                        additionalProperties:\n                                                          type: string\n                                                        type: object\n                                                    type: object\n                                                    x-kubernetes-map-type: atomic\n                                                  name:\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                  path:\n                                                    type: string\n                                                  signerName:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                              configMap:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              downwardAPI:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        fieldRef:\n                                                          properties:\n                                                            apiVersion:\n                                                              type: string\n                                                            fieldPath:\n                                                              type: string\n                                                          required:\n                                                          - fieldPath\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                        resourceFieldRef:\n                                                          properties:\n                                                            containerName:\n                                                              type: string\n                                                            divisor:\n                                                              anyOf:\n                                                              - type: integer\n                                                              - type: string\n                                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                              x-kubernetes-int-or-string: true\n                                                            resource:\n                                                              type: string\n                                                          required:\n                                                          - resource\n                                                          type: object\n                                                          x-kubernetes-map-type: atomic\n                                                      required:\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                type: object\n                                              podCertificate:\n                                                properties:\n                                                  certificateChainPath:\n                                                    type: string\n                                                  credentialBundlePath:\n                                                    type: string\n                                                  keyPath:\n                                                    type: string\n                                                  keyType:\n                                                    type: string\n                                                  maxExpirationSeconds:\n                                                    format: int32\n                                                    type: integer\n                                                  signerName:\n                                                    type: string\n                                                  userAnnotations:\n                                                    additionalProperties:\n                                                      type: string\n                                                    type: object\n                                                required:\n                                                - keyType\n                                                - signerName\n                                                type: object\n                                              secret:\n                                                properties:\n                                                  items:\n                                                    items:\n                                                      properties:\n                                                        key:\n                                                          type: string\n                                                        mode:\n                                                          format: int32\n                                                          type: integer\n                                                        path:\n                                                          type: string\n                                                      required:\n                                                      - key\n                                                      - path\n                                                      type: object\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                  name:\n                                                    default: \"\"\n                                                    type: string\n                                                  optional:\n                                                    type: boolean\n                                                type: object\n                                                x-kubernetes-map-type: atomic\n                                              serviceAccountToken:\n                                                properties:\n                                                  audience:\n                                                    type: string\n                                                  expirationSeconds:\n                                                    format: int64\n                                                    type: integer\n                                                  path:\n                                                    type: string\n                                                required:\n                                                - path\n                                                type: object\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                    quobyte:\n                                      properties:\n                                        group:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        registry:\n                                          type: string\n                                        tenant:\n                                          type: string\n                                        user:\n                                          type: string\n                                        volume:\n                                          type: string\n                                      required:\n                                      - registry\n                                      - volume\n                                      type: object\n                                    rbd:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        image:\n                                          type: string\n                                        keyring:\n                                          default: /etc/ceph/keyring\n                                          type: string\n                                        monitors:\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        pool:\n                                          default: rbd\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        user:\n                                          default: admin\n                                          type: string\n                                      required:\n                                      - image\n                                      - monitors\n                                      type: object\n                                    scaleIO:\n                                      properties:\n                                        fsType:\n                                          default: xfs\n                                          type: string\n                                        gateway:\n                                          type: string\n                                        protectionDomain:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        sslEnabled:\n                                          type: boolean\n                                        storageMode:\n                                          default: ThinProvisioned\n                                          type: string\n                                        storagePool:\n                                          type: string\n                                        system:\n                                          type: string\n                                        volumeName:\n                                          type: string\n                                      required:\n                                      - gateway\n                                      - secretRef\n                                      - system\n                                      type: object\n                                    secret:\n                                      properties:\n                                        defaultMode:\n                                          format: int32\n                                          type: integer\n                                        items:\n                                          items:\n                                            properties:\n                                              key:\n                                                type: string\n                                              mode:\n                                                format: int32\n                                                type: integer\n                                              path:\n                                                type: string\n                                            required:\n                                            - key\n                                            - path\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        optional:\n                                          type: boolean\n                                        secretName:\n                                          type: string\n                                      type: object\n                                    storageos:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        readOnly:\n                                          type: boolean\n                                        secretRef:\n                                          properties:\n                                            name:\n                                              default: \"\"\n                                              type: string\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        volumeName:\n                                          type: string\n                                        volumeNamespace:\n                                          type: string\n                                      type: object\n                                    vsphereVolume:\n                                      properties:\n                                        fsType:\n                                          type: string\n                                        storagePolicyID:\n                                          type: string\n                                        storagePolicyName:\n                                          type: string\n                                        volumePath:\n                                          type: string\n                                      required:\n                                      - volumePath\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - name\n                                x-kubernetes-list-type: map\n                              workloadRef:\n                                properties:\n                                  name:\n                                    type: string\n                                  podGroup:\n                                    type: string\n                                  podGroupReplicaKey:\n                                    type: string\n                                required:\n                                - name\n                                - podGroup\n                                type: object\n                            required:\n                            - containers\n                            type: object\n                        type: object\n                      ttlSecondsAfterFinished:\n                        format: int32\n                        type: integer\n                    required:\n                    - template\n                    type: object\n                type: object\n              schedule:\n                properties:\n                  dayOfMonth:\n                    type: string\n                  dayOfWeek:\n                    type: string\n                  hour:\n                    type: string\n                  minute:\n                    type: string\n                  month:\n                    type: string\n                type: object\n              startingDeadlineSeconds:\n                format: int64\n                minimum: 0\n                type: integer\n              successfulJobsHistoryLimit:\n                format: int32\n                minimum: 0\n                type: integer\n              suspend:\n                type: boolean\n            required:\n            - jobTemplate\n            - schedule\n            type: object\n          status:\n            properties:\n              active:\n                items:\n                  properties:\n                    apiVersion:\n                      type: string\n                    fieldPath:\n                      type: string\n                    kind:\n                      type: string\n                    name:\n                      type: string\n                    namespace:\n                      type: string\n                    resourceVersion:\n                      type: string\n                    uid:\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                maxItems: 10\n                minItems: 1\n                type: array\n                x-kubernetes-list-type: atomic\n              conditions:\n                items:\n                  properties:\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lastScheduleTime:\n                format: date-time\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-role\n  namespace: project-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-admin-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - '*'\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-editor-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-cronjob-viewer-role\nrules:\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-manager-role\nrules:\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs/status\n  verbs:\n  - get\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - batch.tutorial.kubebuilder.io\n  resources:\n  - cronjobs/status\n  verbs:\n  - get\n  - patch\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-leader-election-rolebinding\n  namespace: project-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-controller-manager\n  namespace: project-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager-metrics-service\n  namespace: project-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-webhook-service\n  namespace: project-system\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  selector:\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager\n  namespace: project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        command:\n        - /manager\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /tmp/k8s-metrics-server/metrics-certs\n          name: metrics-certs\n          readOnly: true\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes:\n      - name: metrics-certs\n        secret:\n          items:\n          - key: ca.crt\n            path: ca.crt\n          - key: tls.crt\n            path: tls.crt\n          - key: tls.key\n            path: tls.key\n          optional: false\n          secretName: metrics-server-cert\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-metrics-certs\n  namespace: project-system\nspec:\n  dnsNames:\n  - project-controller-manager-metrics-service.project-system.svc\n  - project-controller-manager-metrics-service.project-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-selfsigned-issuer\n  secretName: metrics-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-serving-cert\n  namespace: project-system\nspec:\n  dnsNames:\n  - project-webhook-service.project-system.svc\n  - project-webhook-service.project-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-selfsigned-issuer\n  secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n  name: project-selfsigned-issuer\n  namespace: project-system\nspec:\n  selfSigned: {}\n---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project\n    control-plane: controller-manager\n  name: project-controller-manager-metrics-monitor\n  namespace: project-system\nspec:\n  endpoints:\n  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n    path: /metrics\n    port: https\n    scheme: https\n    tlsConfig:\n      ca:\n        secret:\n          key: ca.crt\n          name: metrics-server-cert\n      cert:\n        secret:\n          key: tls.crt\n          name: metrics-server-cert\n      insecureSkipVerify: false\n      keySecret:\n        key: tls.key\n        name: metrics-server-cert\n      serverName: project-controller-manager-metrics-service.project-system.svc\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project\n      control-plane: controller-manager\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-system/project-serving-cert\n  name: project-mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /mutate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /mutate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: mcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-system/project-serving-cert\n  name: project-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /validate-batch-tutorial-kubebuilder-io-v1-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v1.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-webhook-service\n      namespace: project-system\n      path: /validate-batch-tutorial-kubebuilder-io-v2-cronjob\n  failurePolicy: Fail\n  name: vcronjob-v2.kb.io\n  rules:\n  - apiGroups:\n    - batch.tutorial.kubebuilder.io\n    apiVersions:\n    - v2\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cronjobs\n  sideEffects: None\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/go.mod",
    "content": "module tutorial.kubebuilder.io/project\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.27.2\n\tgithub.com/onsi/gomega v1.38.2\n\tgithub.com/robfig/cron v1.2.0\n\tk8s.io/api v0.35.0\n\tk8s.io/apimachinery v0.35.0\n\tk8s.io/client-go v0.35.0\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/spf13/cobra v1.10.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.36.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.5.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect\n\tgoogle.golang.org/grpc v1.72.2 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.0 // indirect\n\tk8s.io/apiserver v0.35.0 // indirect\n\tk8s.io/component-base v0.35.0 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=\ngithub.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=\ngithub.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=\ngithub.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=\ngithub.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=\ngithub.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=\ngithub.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=\ngithub.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=\ngithub.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=\ngithub.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=\ngithub.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=\ngithub.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=\ngithub.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=\ngithub.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=\ngithub.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0=\ngithub.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE=\ngithub.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=\ngithub.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=\ngo.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=\ngo.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=\ngo.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=\ngo.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=\ngo.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=\ngo.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=\ngo.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=\ngo.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=\ngo.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=\ngolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=\ngoogle.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=\nk8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=\nk8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=\nk8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=\nk8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=\nk8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4=\nk8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds=\nk8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=\nk8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=\nk8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94=\nk8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=\nsigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=\nsigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/controller/cronjob_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWe'll start out with some imports.  You'll see below that we'll need a few more imports\nthan those scaffolded for us.  We'll talk about each one when we use it.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/robfig/cron\"\n\tkbatch \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tref \"k8s.io/client-go/tools/reference\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n/*\nNext, we'll need a Clock, which will allow us to fake timing in our tests.\n*/\n\n// CronJobReconciler reconciles a CronJob object\ntype CronJobReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n\tClock\n}\n\n/*\nWe'll mock out the clock to make it easier to jump around in time while testing,\nthe \"real\" clock just calls `time.Now`.\n*/\ntype realClock struct{}\n\nfunc (_ realClock) Now() time.Time { return time.Now() } //nolint:staticcheck\n\n// Clock knows how to get the current time.\n// It can be used to fake out timing for testing.\ntype Clock interface {\n\tNow() time.Time\n}\n\n// +kubebuilder:docs-gen:collapse=Clock Code Implementation\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableCronJob represents the status of the CronJob reconciliation\n\ttypeAvailableCronJob = \"Available\"\n\t// typeProgressingCronJob represents the status used when the CronJob is being reconciled\n\ttypeProgressingCronJob = \"Progressing\"\n\t// typeDegradedCronJob represents the status used when the CronJob has encountered an error\n\ttypeDegradedCronJob = \"Degraded\"\n)\n\n/*\nNotice that we need a few more RBAC permissions -- since we're creating and\nmanaging jobs now, we'll need permissions for those, which means adding\na couple more [markers](/reference/markers/rbac.md).\n*/\n\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update\n// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get\n\n/*\nNow, we get to the heart of the controller -- the reconciler logic.\n*/\nvar (\n\tscheduledTimeAnnotation = \"batch.tutorial.kubebuilder.io/scheduled-at\"\n)\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the CronJob object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\n// nolint:gocyclo\nfunc (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t/*\n\t\t### 1: Load the CronJob by name\n\n\t\tWe'll fetch the CronJob using our client.  All client methods take a\n\t\tcontext (to allow for cancellation) as their first argument, and the object\n\t\tin question as their last.  Get is a bit special, in that it takes a\n\t\t[`NamespacedName`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client?tab=doc#ObjectKey)\n\t\tas the middle argument (most don't have a middle argument, as we'll see\n\t\tbelow).\n\n\t\tMany client methods also take variadic options at the end.\n\t*/\n\tvar cronJob batchv1.CronJob\n\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"CronJob resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get CronJob\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Initialize status conditions if not yet present\n\tif len(cronJob.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionUnknown,\n\t\t\tReason:  \"Reconciling\",\n\t\t\tMessage: \"Starting reconciliation\",\n\t\t})\n\t\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to update CronJob status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t/*\n\t\t\tAfter updating the status, we re-fetch the CronJob to ensure we are working with\n\t\t\tthe latest version of the object from the API server.\n\n\t\t\tKubernetes uses optimistic concurrency, meaning that any update (including a\n\t\t\tstatus update) may change the resource version. If we continue reconciliation\n\t\t\twith a stale copy, subsequent updates may fail with a conflict such as:\n\t\t\t\"the object has been modified; please apply your changes to the latest version and try again\".\n\n\t\t\tBy re-fetching here, we keep our reconciliation logic in sync with the actual\n\t\t\tcluster state and avoid unnecessary conflicts and requeues.\n\t\t*/\n\t\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t/*\n\t\t### 2: List all active jobs, and update the status\n\n\t\tTo fully update our status, we'll need to list all child jobs in this namespace that belong to this CronJob.\n\t\tSimilarly to Get, we can use the List method to list the child jobs.  Notice that we use variadic options to\n\t\tset the namespace and field match (which is actually an index lookup that we set up below).\n\t*/\n\tvar childJobs kbatch.JobList\n\tif err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}); err != nil {\n\t\tlog.Error(err, \"unable to list child Jobs\")\n\t\t/*\n\t\t\tBefore updating, ensure we have the latest state of the resource to avoid\n\t\t\tconflict errors (e.g. \"the object has been modified\") that would re-trigger\n\t\t\tthe reconcile loop.\n\t\t*/\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"ReconciliationError\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to list child jobs: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\n\t\t<aside class=\"note\">\n\n\t\t<h1>What is this index about?</h1>\n\n\t\t<p>The reconciler fetches all jobs owned by the cronjob for the status. As our number of cronjobs increases,\n\t\tlooking these up can become quite slow as we have to filter through all of them. For a more efficient lookup,\n\t\tthese jobs will be indexed locally on the controller's name. A jobOwnerKey field is added to the\n\t\tcached job objects. This key references the owning controller and functions as the index. Later in this\n\t\tdocument we will configure the manager to actually index this field.</p>\n\n\t\t</aside>\n\n\t\tOnce we have all the jobs we own, we'll split them into active, successful,\n\t\tand failed jobs, keeping track of the most recent run so that we can record it\n\t\tin status.  Remember, status should be able to be reconstituted from the state\n\t\tof the world, so it's generally not a good idea to read from the status of the\n\t\troot object.  Instead, you should reconstruct it every run.  That's what we'll\n\t\tdo here.\n\n\t\tWe can check if a job is \"finished\" and whether it succeeded or failed using status\n\t\tconditions.  We'll put that logic in a helper to make our code cleaner.\n\t*/\n\n\t// find the active list of jobs\n\tvar activeJobs []*kbatch.Job\n\tvar successfulJobs []*kbatch.Job\n\tvar failedJobs []*kbatch.Job\n\tvar mostRecentTime *time.Time // find the last run so we can update the status\n\n\t/*\n\t\tWe consider a job \"finished\" if it has a \"Complete\" or \"Failed\" condition marked as true.\n\t\tStatus conditions allow us to add extensible status information to our objects that other\n\t\thumans and controllers can examine to check things like completion and health.\n\t*/\n\tisJobFinished := func(job *kbatch.Job) (bool, kbatch.JobConditionType) {\n\t\tfor _, c := range job.Status.Conditions {\n\t\t\tif (c.Type == kbatch.JobComplete || c.Type == kbatch.JobFailed) && c.Status == corev1.ConditionTrue {\n\t\t\t\treturn true, c.Type\n\t\t\t}\n\t\t}\n\n\t\treturn false, \"\"\n\t}\n\t// +kubebuilder:docs-gen:collapse=isJobFinished\n\n\t/*\n\t\tWe'll use a helper to extract the scheduled time from the annotation that\n\t\twe added during job creation.\n\t*/\n\tgetScheduledTimeForJob := func(job *kbatch.Job) (*time.Time, error) {\n\t\ttimeRaw := job.Annotations[scheduledTimeAnnotation]\n\t\tif len(timeRaw) == 0 {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\ttimeParsed, err := time.Parse(time.RFC3339, timeRaw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &timeParsed, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getScheduledTimeForJob\n\n\tfor i, job := range childJobs.Items {\n\t\t_, finishedType := isJobFinished(&job)\n\t\tswitch finishedType {\n\t\tcase \"\": // ongoing\n\t\t\tactiveJobs = append(activeJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobFailed:\n\t\t\tfailedJobs = append(failedJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobComplete:\n\t\t\tsuccessfulJobs = append(successfulJobs, &childJobs.Items[i])\n\t\t}\n\n\t\t// We'll store the launch time in an annotation, so we'll reconstitute that from\n\t\t// the active jobs themselves.\n\t\tscheduledTimeForJob, err := getScheduledTimeForJob(&job)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to parse schedule time for child job\", \"job\", &job)\n\t\t\tcontinue\n\t\t}\n\t\tif scheduledTimeForJob != nil {\n\t\t\tif mostRecentTime == nil || mostRecentTime.Before(*scheduledTimeForJob) {\n\t\t\t\tmostRecentTime = scheduledTimeForJob\n\t\t\t}\n\t\t}\n\t}\n\n\tif mostRecentTime != nil {\n\t\tcronJob.Status.LastScheduleTime = &metav1.Time{Time: *mostRecentTime}\n\t} else {\n\t\tcronJob.Status.LastScheduleTime = nil\n\t}\n\tcronJob.Status.Active = nil\n\tfor _, activeJob := range activeJobs {\n\t\tjobRef, err := ref.GetReference(r.Scheme, activeJob)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to make reference to active job\", \"job\", activeJob)\n\t\t\tcontinue\n\t\t}\n\t\tcronJob.Status.Active = append(cronJob.Status.Active, *jobRef)\n\t}\n\n\t/*\n\t\tHere, we'll log how many jobs we observed at a slightly higher logging level,\n\t\tfor debugging.  Notice how instead of using a format string, we use a fixed message,\n\t\tand attach key-value pairs with the extra information.  This makes it easier to\n\t\tfilter and query log lines.\n\t*/\n\tlog.V(1).Info(\"job count\", \"active jobs\", len(activeJobs), \"successful jobs\", len(successfulJobs), \"failed jobs\", len(failedJobs))\n\n\t// Check if CronJob is suspended\n\tisSuspended := cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend\n\n\t// Update status conditions based on current state\n\tif isSuspended {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"Suspended\",\n\t\t\tMessage: \"CronJob is suspended\",\n\t\t})\n\t} else if len(failedJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t} else if len(activeJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) are currently active\", len(activeJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"CronJob is progressing with %d active job(s)\", len(activeJobs)),\n\t\t})\n\t} else {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"AllJobsCompleted\",\n\t\t\tMessage: \"All jobs have completed successfully\",\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"NoJobsActive\",\n\t\t\tMessage: \"No jobs are currently active\",\n\t\t})\n\t}\n\n\t/*\n\t\tUsing the data we've gathered, we'll update the status of our CRD.\n\t\tJust like before, we use our client.  To specifically update the status\n\t\tsubresource, we'll use the `Status` part of the client, with the `Update`\n\t\tmethod.\n\n\t\tThe status subresource ignores changes to spec, so it's less likely to conflict\n\t\twith any other updates, and can have separate permissions.\n\t*/\n\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\tlog.Error(err, \"unable to update CronJob status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\t\tOnce we've updated our status, we can move on to ensuring that the status of\n\t\tthe world matches what we want in our spec.\n\n\t\t### 3: Clean up old jobs according to the history limit\n\n\t\tFirst, we'll try to clean up old jobs, so that we don't leave too many lying\n\t\taround.\n\t*/\n\n\t// NB: deleting these are \"best effort\" -- if we fail on a particular one,\n\t// we won't requeue just to finish the deleting.\n\tif cronJob.Spec.FailedJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(failedJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range failedJobs {\n\t\t\tif int32(i) >= int32(len(failedJobs))-*cronJob.Spec.FailedJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old failed job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old failed job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(successfulJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range successfulJobs {\n\t\t\tif int32(i) >= int32(len(successfulJobs))-*cronJob.Spec.SuccessfulJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old successful job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old successful job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ### 4: Check if we're suspended\n\n\tIf this object is suspended, we don't want to run any jobs, so we'll stop now.\n\tThis is useful if something's broken with the job we're running and we want to\n\tpause runs to investigate or putz with the cluster, without deleting the object.\n\t*/\n\n\tif cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend {\n\t\tlog.V(1).Info(\"cronjob suspended, skipping\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\t### 5: Get the next scheduled run\n\n\t\tIf we're not paused, we'll need to calculate the next scheduled run, and whether\n\t\tor not we've got a run that we haven't processed yet.\n\t*/\n\n\t/*\n\t\tWe'll calculate the next scheduled time using our helpful cron library.\n\t\tWe'll start calculating appropriate times from our last run, or the creation\n\t\tof the CronJob if we can't find a last run.\n\n\t\tIf there are too many missed runs and we don't have any deadlines set, we'll\n\t\tbail so that we don't cause issues on controller restarts or wedges.\n\n\t\tOtherwise, we'll just return the missed runs (of which we'll just use the latest),\n\t\tand the next run, so that we can know when it's time to reconcile again.\n\t*/\n\tgetNextSchedule := func(cronJob *batchv1.CronJob, now time.Time) (lastMissed time.Time, next time.Time, err error) {\n\t\tsched, err := cron.ParseStandard(cronJob.Spec.Schedule)\n\t\tif err != nil {\n\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"unparseable schedule %q: %w\", cronJob.Spec.Schedule, err)\n\t\t}\n\n\t\t// for optimization purposes, cheat a bit and start from our last observed run time\n\t\t// we could reconstitute this here, but there's not much point, since we've\n\t\t// just updated it.\n\t\tvar earliestTime time.Time\n\t\tif cronJob.Status.LastScheduleTime != nil {\n\t\t\tearliestTime = cronJob.Status.LastScheduleTime.Time\n\t\t} else {\n\t\t\tearliestTime = cronJob.CreationTimestamp.Time\n\t\t}\n\t\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\t\t// controller is not going to schedule anything below this point\n\t\t\tschedulingDeadline := now.Add(-time.Second * time.Duration(*cronJob.Spec.StartingDeadlineSeconds))\n\n\t\t\tif schedulingDeadline.After(earliestTime) {\n\t\t\t\tearliestTime = schedulingDeadline\n\t\t\t}\n\t\t}\n\t\tif earliestTime.After(now) {\n\t\t\treturn time.Time{}, sched.Next(now), nil\n\t\t}\n\n\t\tstarts := 0\n\t\tfor t := sched.Next(earliestTime); !t.After(now); t = sched.Next(t) {\n\t\t\tlastMissed = t\n\t\t\t// An object might miss several starts. For example, if\n\t\t\t// controller gets wedged on Friday at 5:01pm when everyone has\n\t\t\t// gone home, and someone comes in on Tuesday AM and discovers\n\t\t\t// the problem and restarts the controller, then all the hourly\n\t\t\t// jobs, more than 80 of them for one hourly scheduledJob, should\n\t\t\t// all start running with no further intervention (if the scheduledJob\n\t\t\t// allows concurrency and late starts).\n\t\t\t//\n\t\t\t// However, if there is a bug somewhere, or incorrect clock\n\t\t\t// on controller's server or apiservers (for setting creationTimestamp)\n\t\t\t// then there could be so many missed start times (it could be off\n\t\t\t// by decades or more), that it would eat up all the CPU and memory\n\t\t\t// of this controller. In that case, we want to not try to list\n\t\t\t// all the missed start times.\n\t\t\tstarts++\n\t\t\tif starts > 100 {\n\t\t\t\t// We can't get the most recent times so just return an empty slice\n\t\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"Too many missed start times (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.\") //nolint:staticcheck\n\t\t\t}\n\t\t}\n\t\treturn lastMissed, sched.Next(now), nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getNextSchedule\n\n\t// figure out the next times that we need to create\n\t// jobs at (or anything we missed).\n\tmissedRun, nextRun, err := getNextSchedule(&cronJob, r.Now())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to figure out CronJob schedule\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the schedule error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"InvalidSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to parse schedule: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\t// we don't really care about requeuing until we get an update that\n\t\t// fixes the schedule, so don't return an error\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\tWe'll prep our eventual request to requeue until the next job, and then figure\n\t\tout if we actually need to run.\n\t*/\n\tscheduledResult := ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())} // save this so we can re-use it elsewhere\n\tlog = log.WithValues(\"now\", r.Now(), \"next run\", nextRun)\n\n\t/*\n\t\t### 6: Run a new job if it's on schedule, not past the deadline, and not blocked by our concurrency policy\n\n\t\tIf we've missed a run, and we're still within the deadline to start it, we'll need to run a job.\n\t*/\n\tif missedRun.IsZero() {\n\t\tlog.V(1).Info(\"no upcoming scheduled times, sleeping until next\")\n\t\treturn scheduledResult, nil\n\t}\n\n\t// make sure we're not too late to start the run\n\tlog = log.WithValues(\"current run\", missedRun)\n\ttooLate := false\n\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\ttooLate = missedRun.Add(time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second).Before(r.Now())\n\t}\n\tif tooLate {\n\t\tlog.V(1).Info(\"missed starting deadline for last run, sleeping till next\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect missed deadline\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"MissedSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Missed starting deadline for run at %v\", missedRun),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn scheduledResult, nil\n\t}\n\n\t/*\n\t\tIf we actually have to run a job, we'll need to either wait till existing ones finish,\n\t\treplace the existing ones, or just add new ones.  If our information is out of date due\n\t\tto cache delay, we'll get a requeue when we get up-to-date information.\n\t*/\n\t// figure out how to run this job -- concurrency policy might forbid us from running\n\t// multiple at the same time...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ForbidConcurrent && len(activeJobs) > 0 {\n\t\tlog.V(1).Info(\"concurrency policy blocks concurrent runs, skipping\", \"num active\", len(activeJobs))\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...or instruct us to replace existing ones...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ReplaceConcurrent {\n\t\tfor _, activeJob := range activeJobs {\n\t\t\t// we don't care if the job was already deleted\n\t\t\tif err := r.Delete(ctx, activeJob, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete active job\", \"job\", activeJob)\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t\tOnce we've figured out what to do with existing jobs, we'll actually create our desired job\n\t*/\n\n\t/*\n\t\tWe need to construct a job based on our CronJob's template.  We'll copy over the spec\n\t\tfrom the template and copy some basic object meta.\n\n\t\tThen, we'll set the \"scheduled time\" annotation so that we can reconstitute our\n\t\t`LastScheduleTime` field each reconcile.\n\n\t\tFinally, we'll need to set an owner reference.  This allows the Kubernetes garbage collector\n\t\tto clean up jobs when we delete the CronJob, and allows controller-runtime to figure out\n\t\twhich cronjob needs to be reconciled when a given job changes (is added, deleted, completes, etc).\n\t*/\n\tconstructJobForCronJob := func(cronJob *batchv1.CronJob, scheduledTime time.Time) (*kbatch.Job, error) {\n\t\t// We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice\n\t\tname := fmt.Sprintf(\"%s-%d\", cronJob.Name, scheduledTime.Unix())\n\n\t\tjob := &kbatch.Job{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tLabels:      make(map[string]string),\n\t\t\t\tAnnotations: make(map[string]string),\n\t\t\t\tName:        name,\n\t\t\t\tNamespace:   cronJob.Namespace,\n\t\t\t},\n\t\t\tSpec: *cronJob.Spec.JobTemplate.Spec.DeepCopy(),\n\t\t}\n\t\tmaps.Copy(job.Annotations, cronJob.Spec.JobTemplate.Annotations)\n\t\tjob.Annotations[scheduledTimeAnnotation] = scheduledTime.Format(time.RFC3339)\n\t\tmaps.Copy(job.Labels, cronJob.Spec.JobTemplate.Labels)\n\t\tif err := ctrl.SetControllerReference(cronJob, job, r.Scheme); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn job, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=constructJobForCronJob\n\n\t// actually make the job...\n\tjob, err := constructJobForCronJob(&cronJob, missedRun)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to construct job from template\")\n\t\t// don't bother requeuing until we get a change to the spec\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...and create it on the cluster\n\tif err := r.Create(ctx, job); err != nil {\n\t\tlog.Error(err, \"unable to create Job for CronJob\", \"job\", job)\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobCreationFailed\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to create job: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tlog.V(1).Info(\"created Job for CronJob run\", \"job\", job)\n\n\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\treturn ctrl.Result{}, fetchErr\n\t}\n\t// Update status condition to reflect successful job creation\n\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\tType:    typeProgressingCronJob,\n\t\tStatus:  metav1.ConditionTrue,\n\t\tReason:  \"JobCreated\",\n\t\tMessage: fmt.Sprintf(\"Created job %s\", job.Name),\n\t})\n\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t}\n\n\t/*\n\t\t### 7: Requeue when we either see a running job or it's time for the next scheduled run\n\n\t\tFinally, we'll return the result that we prepped above, that says we want to requeue\n\t\twhen our next run would need to occur.  This is taken as a maximum deadline -- if something\n\t\telse changes in between, like our job starts or finishes, we get modified, etc, we might\n\t\treconcile again sooner.\n\t*/\n\t// we'll requeue once we see the running job, and update our status\n\treturn scheduledResult, nil\n}\n\n/*\n### Setup\n\nFinally, we'll update our setup.  In order to allow our reconciler to quickly\nlook up Jobs by their owner, we'll need an index.  We declare an index key that\nwe can later use with the client as a pseudo-field name, and then describe how to\nextract the indexed value from the Job object.  The indexer will automatically take\ncare of namespaces for us, so we just have to extract the owner name if the Job has\na CronJob owner.\n\nAdditionally, we'll inform the manager that this controller owns some Jobs, so that it\nwill automatically call Reconcile on the underlying CronJob when a Job changes, is\ndeleted, etc.\n*/\nvar (\n\tjobOwnerKey = \".metadata.controller\"\n\tapiGVStr    = batchv1.GroupVersion.String()\n)\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\t// set up a real clock, since we're not in a test\n\tif r.Clock == nil {\n\t\tr.Clock = realClock{}\n\t}\n\n\tif err := mgr.GetFieldIndexer().IndexField(context.Background(), &kbatch.Job{}, jobOwnerKey, func(rawObj client.Object) []string {\n\t\t// grab the job object, extract the owner...\n\t\tjob := rawObj.(*kbatch.Job)\n\t\towner := metav1.GetControllerOf(job)\n\t\tif owner == nil {\n\t\t\treturn nil\n\t\t}\n\t\t// ...make sure it's a CronJob...\n\t\tif owner.APIVersion != apiGVStr || owner.Kind != \"CronJob\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// ...and if so, return it\n\t\treturn []string{owner.Name}\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&batchv1.CronJob{}).\n\t\tOwns(&kbatch.Job{}).\n\t\tNamed(\"cronjob\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/controller/cronjob_controller_test.go",
    "content": "/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\n\tcronjobv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\nvar _ = Describe(\"CronJob controller\", func() {\n\tContext(\"CronJob controller test\", func() {\n\n\t\tconst NamespaceName = \"test-cronjob\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &v1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      NamespaceName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t},\n\t\t}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Get(ctx, types.NamespacedName{Name: NamespaceName}, &v1.Namespace{})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\terr = k8sClient.Create(ctx, namespace)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// Note: We don't delete the namespace here to avoid issues with parallel test execution.\n\t\t\t// The namespace will be cleaned up when the test suite finishes.\n\t\t})\n\n\t\tIt(\"should initialize status conditions on first reconciliation\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that status conditions are initialized\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Conditions).NotTo(BeEmpty())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set AllJobsCompleted condition when no active jobs exist\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that the CronJob has zero active Jobs\")\n\t\t\tConsistently(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(BeEmpty())\n\t\t\t}).WithTimeout(time.Second * 5).WithPolling(time.Millisecond * 250).Should(Succeed())\n\n\t\t\tBy(\"Checking AllJobsCompleted condition\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tif len(availableConditions) > 0 {\n\t\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"AllJobsCompleted\"))\n\t\t\t}\n\n\t\t\tvar progressingConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Progressing\")), &progressingConditions))\n\t\t\tif len(progressingConditions) > 0 {\n\t\t\t\tExpect(progressingConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\tExpect(progressingConditions[0].Reason).To(Equal(\"NoJobsActive\"))\n\t\t\t}\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should track active jobs and set JobsActive condition\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Creating an active Job owned by the CronJob\")\n\t\t\ttestJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"test-job-%d\", GinkgoRandomSeed()),\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\ttestJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, testJob)).To(Succeed())\n\n\t\t\ttestJob.Status.Active = 2\n\t\t\tExpect(k8sClient.Status().Update(ctx, testJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that the CronJob has one active Job in status\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(HaveLen(1))\n\t\t\t\tg.Expect(cronJob.Status.Active[0].Name).To(Equal(testJob.Name))\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking JobsActive conditions\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tExpect(availableConditions).To(HaveLen(1))\n\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"JobsActive\"))\n\n\t\t\tvar progressingConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Progressing\")), &progressingConditions))\n\t\t\tExpect(progressingConditions).To(HaveLen(1))\n\t\t\tExpect(progressingConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\tExpect(progressingConditions[0].Reason).To(Equal(\"JobsActive\"))\n\n\t\t\tBy(\"Cleaning up\")\n\t\t\tExpect(k8sClient.Delete(ctx, testJob)).To(Succeed())\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set Degraded condition when jobs fail\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Creating a failed Job owned by the CronJob\")\n\t\t\tfailedJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"test-job-failed-%d\", GinkgoRandomSeed()),\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\tfailedJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, failedJob)).To(Succeed())\n\n\t\t\tnow := metav1.Now()\n\t\t\tfailedJob.Status.StartTime = &now\n\t\t\tfailedJob.Status.Conditions = append(failedJob.Status.Conditions,\n\t\t\t\tbatchv1.JobCondition{\n\t\t\t\t\tType:   batchv1.JobFailureTarget,\n\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t},\n\t\t\t\tbatchv1.JobCondition{\n\t\t\t\t\tType:   batchv1.JobFailed,\n\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t})\n\t\t\tExpect(k8sClient.Status().Update(ctx, failedJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that Degraded=True when jobs fail\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tvar degradedConditions []metav1.Condition\n\t\t\t\tg.Expect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\t\tHaveField(\"Type\", Equal(\"Degraded\")), &degradedConditions))\n\t\t\t\tif len(degradedConditions) > 0 {\n\t\t\t\t\tg.Expect(degradedConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\t\t\tg.Expect(degradedConditions[0].Reason).To(Equal(\"JobsFailed\"))\n\t\t\t\t}\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking that Available=False when jobs fail\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tif len(availableConditions) > 0 {\n\t\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"JobsFailed\"))\n\t\t\t}\n\n\t\t\tBy(\"Cleaning up\")\n\t\t\tExpect(k8sClient.Delete(ctx, failedJob)).To(Succeed())\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set Available=False when CronJob is suspended\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Updating the CronJob to suspend it\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tcronJob.Spec.Suspend = ptr.To(true)\n\t\t\t\tg.Expect(k8sClient.Update(ctx, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking that Available=False when suspended\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tvar availableConditions []metav1.Condition\n\t\t\t\tg.Expect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\t\tif len(availableConditions) > 0 {\n\t\t\t\t\tg.Expect(availableConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\t\tg.Expect(availableConditions[0].Reason).To(Equal(\"Suspended\"))\n\t\t\t\t}\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/controller/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWhen we created the CronJob API with `kubebuilder create api` in a [previous chapter](/cronjob-tutorial/new-api.md), Kubebuilder already did some test work for you.\nKubebuilder scaffolded a `internal/controller/suite_test.go` file that does the bare bones of setting up a test environment.\n\nFirst, it will contain the necessary imports.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNow, let's go through the code generated.\n*/\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client // You'll be using this client in your tests.\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\t/*\n\t\tThe CronJob Kind is added to the runtime scheme used by the test environment.\n\t\tThis ensures that the CronJob API is registered with the scheme, allowing the test controller to recognize and interact with CronJob resources.\n\t*/\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\t/*\n\t\tAfter the schemas, you will see the following marker.\n\t\tThis marker is what allows new schemas to be added here automatically when a new API is added to the project.\n\t*/\n\n\t// +kubebuilder:scaffold:scheme\n\n\t/*\n\t\tThe envtest environment is configured to load Custom Resource Definitions (CRDs) from the specified directory.\n\t\tThis setup enables the test environment to recognize and interact with the custom resources defined by these CRDs.\n\t*/\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\t/*\n\t\tThen, we start the envtest cluster.\n\t*/\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\t/*\n\t\tA client is created for our test CRUD operations.\n\t*/\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t/*\n\t\tOne thing that this autogenerated file is missing, however, is a way to actually start your controller.\n\t\tThe code above will set up a client for interacting with your custom Kind,\n\t\tbut will not be able to test your controller behavior.\n\t\tIf you want to test your custom controller logic, you’ll need to add some familiar-looking manager logic\n\t\tto your BeforeSuite() function, so you can register your custom controller to run on this test cluster.\n\n\t\tYou may notice that the code below runs your controller with nearly identical logic to your CronJob project’s main.go!\n\t\tThe only difference is that the manager is started in a separate goroutine so it does not block the cleanup of envtest\n\t\twhen you’re done running your tests.\n\n\t\tNote that we set up both a \"live\" k8s client and a separate client from the manager. This is because when making\n\t\tassertions in tests, you generally want to assert against the live state of the API server. If you use the client\n\t\tfrom the manager (`k8sManager.GetClient`), you'd end up asserting against the contents of the cache instead, which is\n\t\tslower and can introduce flakiness into your tests. We could use the manager's `APIReader` to accomplish the same\n\t\tthing, but that would leave us with two clients in our test assertions and setup (one for reading, one for writing),\n\t\tand it'd be easy to make mistakes.\n\n\t\tNote that we keep the reconciler running against the manager's cache client, though -- we want our controller to\n\t\tbehave as it would in production, and we use features of the cache (like indices) in our controller which aren't\n\t\tavailable when talking directly to the API server.\n\t*/\n\tk8sManager, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t})\n\tExpect(err).ToNot(HaveOccurred())\n\n\terr = (&CronJobReconciler{\n\t\tClient: k8sManager.GetClient(),\n\t\tScheme: k8sManager.GetScheme(),\n\t}).SetupWithManager(k8sManager)\n\tExpect(err).ToNot(HaveOccurred())\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = k8sManager.Start(ctx)\n\t\tExpect(err).ToNot(HaveOccurred(), \"failed to run manager\")\n\t}()\n})\n\n/*\nKubebuilder also generates boilerplate functions for cleaning up envtest and actually running your test files in your controllers/ directory.\nYou won't need to touch these.\n*/\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n/*\nNow that you have your controller running on a test cluster and a client ready to perform operations on your CronJob, we can start writing integration tests!\n*/\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v1\n\nimport (\n\t\"context\"\n\n\t\"github.com/robfig/cron\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tvalidationutils \"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNext, we'll setup a logger for the webhooks.\n*/\n\nvar cronjoblog = logf.Log.WithName(\"cronjob-resource\")\n\n/*\nThis setup doubles as setup for our conversion webhooks: as long as our\ntypes implement the\n[Hub](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub) and\n[Convertible](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible)\ninterfaces, a conversion webhook will be registered.\n\n*/\n\n// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.\nfunc SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &batchv1.CronJob{}).\n\t\tWithValidator(&CronJobCustomValidator{}).\n\t\tWithDefaulter(&CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}).\n\t\tComplete()\n}\n\n/*\nNotice that we use kubebuilder markers to generate webhook manifests.\nThis marker is responsible for generating a mutating webhook manifest.\n\nThe meaning of each marker can be found [here](/reference/markers/webhook.md).\n*/\n\n/*\nThis marker is responsible for generating a mutation webhook manifest.\n*/\n\n// +kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=mcronjob-v1.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind CronJob when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomDefaulter struct {\n\n\t// Default values for various CronJob fields\n\tDefaultConcurrencyPolicy          batchv1.ConcurrencyPolicy\n\tDefaultSuspend                    bool\n\tDefaultSuccessfulJobsHistoryLimit int32\n\tDefaultFailedJobsHistoryLimit     int32\n}\n\n/*\nWe use the `webhook.CustomDefaulter`interface to set defaults to our CRD.\nA webhook will automatically be served that calls this defaulting.\n\nThe `Default`method is expected to mutate the receiver, setting the defaults.\n*/\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.\nfunc (d *CronJobCustomDefaulter) Default(_ context.Context, obj *batchv1.CronJob) error {\n\tcronjoblog.Info(\"Defaulting for CronJob\", \"name\", obj.GetName())\n\n\t// Set default values\n\td.applyDefaults(obj)\n\treturn nil\n}\n\n// applyDefaults applies default values to CronJob fields.\nfunc (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) {\n\tif cronJob.Spec.ConcurrencyPolicy == \"\" {\n\t\tcronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy\n\t}\n\tif cronJob.Spec.Suspend == nil {\n\t\tcronJob.Spec.Suspend = new(bool)\n\t\t*cronJob.Spec.Suspend = d.DefaultSuspend\n\t}\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit == nil {\n\t\tcronJob.Spec.SuccessfulJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit\n\t}\n\tif cronJob.Spec.FailedJobsHistoryLimit == nil {\n\t\tcronJob.Spec.FailedJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit\n\t}\n}\n\n/*\nWe can validate our CRD beyond what's possible with declarative\nvalidation. Generally, declarative validation should be sufficient, but\nsometimes more advanced use cases call for complex validation.\n\nFor instance, we'll see below that we use this to validate a well-formed cron\nschedule without making up a long regular expression.\n\nIf `webhook.CustomValidator` interface is implemented, a webhook will automatically be\nserved that calls the validation.\n\nThe `ValidateCreate`, `ValidateUpdate` and `ValidateDelete` methods are expected\nto validate its receiver upon creation, update and deletion respectively.\nWe separate out ValidateCreate from ValidateUpdate to allow behavior like making\ncertain fields immutable, so that they can only be set on creation.\nValidateDelete is also separated from ValidateUpdate to allow different\nvalidation behavior on deletion.\nHere, however, we just use the same shared validation for `ValidateCreate` and\n`ValidateUpdate`. And we do nothing in `ValidateDelete`, since we don't need to\nvalidate anything on deletion.\n*/\n\n/*\nThis marker is responsible for generating a validation webhook manifest.\n*/\n\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v1-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v1,name=vcronjob-v1.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomValidator struct is responsible for validating the CronJob resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomValidator struct { // +kubebuilder:docs-gen:collapse=Remaining Webhook Code\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateCreate(_ context.Context, obj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon creation\", \"name\", obj.GetName())\n\n\treturn nil, validateCronJob(obj)\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon update\", \"name\", newObj.GetName())\n\n\treturn nil, validateCronJob(newObj)\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateDelete(_ context.Context, obj *batchv1.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n\n/*\nWe validate the name and the spec of the CronJob.\n*/\n\n// validateCronJob validates the fields of a CronJob object.\nfunc validateCronJob(cronjob *batchv1.CronJob) error {\n\tvar allErrs field.ErrorList\n\tif err := validateCronJobName(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif err := validateCronJobSpec(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif len(allErrs) == 0 {\n\t\treturn nil\n\t}\n\n\treturn apierrors.NewInvalid(\n\t\tschema.GroupKind{Group: \"batch.tutorial.kubebuilder.io\", Kind: \"CronJob\"},\n\t\tcronjob.Name, allErrs)\n}\n\n/*\nSome fields are declaratively validated by OpenAPI schema.\nYou can find kubebuilder validation markers (prefixed\nwith `// +kubebuilder:validation`) in the\n[Designing an API](api-design.md) section.\nYou can find all of the kubebuilder supported markers for\ndeclaring validation by running `controller-gen crd -w`,\nor [here](/reference/markers/crd-validation.md).\n*/\n\nfunc validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error {\n\t// The field helpers from the kubernetes API machinery help us return nicely\n\t// structured validation errors.\n\treturn validateScheduleFormat(\n\t\tcronjob.Spec.Schedule,\n\t\tfield.NewPath(\"spec\").Child(\"schedule\"))\n}\n\n/*\nWe'll need to validate the [cron](https://en.wikipedia.org/wiki/Cron) schedule\nis well-formatted.\n*/\n\nfunc validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error {\n\tif _, err := cron.ParseStandard(schedule); err != nil {\n\t\treturn field.Invalid(fldPath, schedule, err.Error())\n\t}\n\treturn nil\n}\n\n/*\nValidating the length of a string field can be done declaratively by\nthe validation schema.\n\nBut the `ObjectMeta.Name` field is defined in a shared package under\nthe apimachinery repo, so we can't declaratively validate it using\nthe validation schema.\n*/\n\nfunc validateCronJobName(cronjob *batchv1.CronJob) *field.Error {\n\tif len(cronjob.Name) > validationutils.DNS1035LabelMaxLength-11 {\n\t\t// The job name length is 63 characters like all Kubernetes objects\n\t\t// (which must fit in a DNS subdomain). The cronjob controller appends\n\t\t// a 11-character suffix to the cronjob (`-$TIMESTAMP`) when creating\n\t\t// a job. The job name length limit is 63 characters. Therefore cronjob\n\t\t// names must have length <= 63-11=52. If we don't validate this here,\n\t\t// then job creation will fail later.\n\t\treturn field.Invalid(field.NewPath(\"metadata\").Child(\"name\"), cronjob.Name, \"must be no more than 52 characters\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/cronjob_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n\t\"k8s.io/utils/ptr\"\n)\n\nvar _ = Describe(\"CronJob Webhook\", func() {\n\tvar (\n\t\tobj       *batchv1.CronJob\n\t\toldObj    *batchv1.CronJob\n\t\tvalidator CronJobCustomValidator\n\t\tdefaulter CronJobCustomDefaulter\n\t)\n\n\tconst validCronJobName = \"valid-cronjob-name\"\n\tconst schedule = \"*/5 * * * *\"\n\n\tBeforeEach(func() {\n\t\tobj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*obj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*obj.Spec.FailedJobsHistoryLimit = 1\n\n\t\toldObj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*oldObj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*oldObj.Spec.FailedJobsHistoryLimit = 1\n\n\t\tvalidator = CronJobCustomValidator{}\n\t\tdefaulter = CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}\n\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating CronJob under Defaulting Webhook\", func() {\n\t\tIt(\"Should apply defaults when a required field is empty\", func() {\n\t\t\tBy(\"simulating a scenario where defaults should be applied\")\n\t\t\tobj.Spec.ConcurrencyPolicy = \"\"           // This should default to AllowConcurrent\n\t\t\tobj.Spec.Suspend = nil                    // This should default to false\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = nil // This should default to 3\n\t\t\tobj.Spec.FailedJobsHistoryLimit = nil     // This should default to 1\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\n\t\t\tBy(\"checking that the default values are set\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), \"Expected ConcurrencyPolicy to default to AllowConcurrent\")\n\t\t\tExpect(*obj.Spec.Suspend).To(BeFalse(), \"Expected Suspend to default to false\")\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), \"Expected SuccessfulJobsHistoryLimit to default to 3\")\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), \"Expected FailedJobsHistoryLimit to default to 1\")\n\t\t})\n\n\t\tIt(\"Should not overwrite fields that are already set\", func() {\n\t\t\tBy(\"setting fields that would normally get a default\")\n\t\t\tobj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent\n\t\t\tobj.Spec.Suspend = ptr.To(true)\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = ptr.To(int32(5))\n\t\t\tobj.Spec.FailedJobsHistoryLimit = ptr.To(int32(2))\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\n\t\t\tBy(\"checking that the fields were not overwritten\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), \"Expected ConcurrencyPolicy to retain its set value\")\n\t\t\tExpect(obj.Spec.Suspend).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.Suspend).To(BeTrue(), \"Expected Suspend to retain its set value\")\n\t\t\tExpect(obj.Spec.SuccessfulJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), \"Expected SuccessfulJobsHistoryLimit to retain its set value\")\n\t\t\tExpect(obj.Spec.FailedJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), \"Expected FailedJobsHistoryLimit to retain its set value\")\n\t\t})\n\t})\n\n\tContext(\"When creating or updating CronJob under Validating Webhook\", func() {\n\t\tIt(\"Should deny creation if the name is too long\", func() {\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"must be no more than 52 characters\")),\n\t\t\t\t\"Expected name validation to fail for a too-long name\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the name is valid\", func() {\n\t\t\tobj.Name = validCronJobName\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected name validation to pass for a valid name\")\n\t\t})\n\n\t\tIt(\"Should deny creation if the schedule is invalid\", func() {\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"Expected exactly 5 fields, found 1: invalid-cron-schedule\")),\n\t\t\t\t\"Expected spec validation to fail for an invalid schedule\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the schedule is valid\", func() {\n\t\t\tobj.Spec.Schedule = schedule\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected spec validation to pass for a valid schedule\")\n\t\t})\n\n\t\tIt(\"Should deny update if both name and spec are invalid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).Error().To(HaveOccurred(),\n\t\t\t\t\"Expected validation to fail for both name and spec\")\n\t\t})\n\n\t\tIt(\"Should admit update if both name and spec are valid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"valid-cronjob-name-updated\"\n\t\t\tobj.Spec.Schedule = \"0 0 * * *\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil(),\n\t\t\t\t\"Expected validation to pass for a valid update\")\n\t\t})\n\t})\n\n\tContext(\"When creating CronJob under Conversion Webhook\", func() {\n\t\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t\t// Example:\n\t\t// It(\"Should convert the object correctly\", func() {\n\t\t//     convertedObj := &batchv1.CronJob{}\n\t\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t\t//     Expect(convertedObj).ToNot(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCronJobWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\npackage v2\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/robfig/cron\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tvalidationutils \"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\tbatchv2 \"tutorial.kubebuilder.io/project/api/v2\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n// nolint:unused\n// log is for logging in this package.\nvar cronjoblog = logf.Log.WithName(\"cronjob-resource\")\n\n// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.\nfunc SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &batchv2.CronJob{}).\n\t\tWithValidator(&CronJobCustomValidator{}).\n\t\tWithDefaulter(&CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv2.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-batch-tutorial-kubebuilder-io-v2-cronjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v2,name=mcronjob-v2.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind CronJob when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomDefaulter struct {\n\t// Default values for various CronJob fields\n\tDefaultConcurrencyPolicy          batchv2.ConcurrencyPolicy\n\tDefaultSuspend                    bool\n\tDefaultSuccessfulJobsHistoryLimit int32\n\tDefaultFailedJobsHistoryLimit     int32\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.\nfunc (d *CronJobCustomDefaulter) Default(_ context.Context, obj *batchv2.CronJob) error {\n\tcronjoblog.Info(\"Defaulting for CronJob\", \"name\", obj.GetName())\n\n\t// Set default values\n\td.applyDefaults(obj)\n\treturn nil\n\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-batch-tutorial-kubebuilder-io-v2-cronjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=create;update,versions=v2,name=vcronjob-v2.kb.io,admissionReviewVersions=v1\n\n// CronJobCustomValidator struct is responsible for validating the CronJob resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateCreate(_ context.Context, obj *batchv2.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon creation\", \"name\", obj.GetName())\n\n\treturn nil, validateCronJob(obj)\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *batchv2.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon update\", \"name\", newObj.GetName())\n\n\treturn nil, validateCronJob(newObj)\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type CronJob.\nfunc (v *CronJobCustomValidator) ValidateDelete(_ context.Context, obj *batchv2.CronJob) (admission.Warnings, error) {\n\tcronjoblog.Info(\"Validation for CronJob upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n\n// applyDefaults applies default values to CronJob fields.\nfunc (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv2.CronJob) {\n\tif cronJob.Spec.ConcurrencyPolicy == \"\" {\n\t\tcronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy\n\t}\n\tif cronJob.Spec.Suspend == nil {\n\t\tcronJob.Spec.Suspend = new(bool)\n\t\t*cronJob.Spec.Suspend = d.DefaultSuspend\n\t}\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit == nil {\n\t\tcronJob.Spec.SuccessfulJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit\n\t}\n\tif cronJob.Spec.FailedJobsHistoryLimit == nil {\n\t\tcronJob.Spec.FailedJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit\n\t}\n}\n\n// +kubebuilder:docs-gen:collapse=Webhook Setup and Defaulting\n\n// validateCronJob validates the fields of a CronJob object.\nfunc validateCronJob(cronjob *batchv2.CronJob) error {\n\tvar allErrs field.ErrorList\n\tif err := validateCronJobName(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif err := validateCronJobSpec(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif len(allErrs) == 0 {\n\t\treturn nil\n\t}\n\treturn apierrors.NewInvalid(schema.GroupKind{Group: \"batch.tutorial.kubebuilder.io\", Kind: \"CronJob\"}, cronjob.Name, allErrs)\n}\n\nfunc validateCronJobName(cronjob *batchv2.CronJob) *field.Error {\n\tif len(cronjob.Name) > validationutils.DNS1035LabelMaxLength-11 {\n\t\treturn field.Invalid(field.NewPath(\"metadata\").Child(\"name\"), cronjob.Name, \"must be no more than 52 characters\")\n\t}\n\treturn nil\n}\n\n// validateCronJobSpec validates the schedule format of the custom CronSchedule type\nfunc validateCronJobSpec(cronjob *batchv2.CronJob) *field.Error {\n\t// Build cron expression from the parts\n\tparts := []string{\"*\", \"*\", \"*\", \"*\", \"*\"} // default parts for minute, hour, day of month, month, day of week\n\tif cronjob.Spec.Schedule.Minute != nil {\n\t\tparts[0] = string(*cronjob.Spec.Schedule.Minute) // Directly cast CronField (which is an alias of string) to string\n\t}\n\tif cronjob.Spec.Schedule.Hour != nil {\n\t\tparts[1] = string(*cronjob.Spec.Schedule.Hour)\n\t}\n\tif cronjob.Spec.Schedule.DayOfMonth != nil {\n\t\tparts[2] = string(*cronjob.Spec.Schedule.DayOfMonth)\n\t}\n\tif cronjob.Spec.Schedule.Month != nil {\n\t\tparts[3] = string(*cronjob.Spec.Schedule.Month)\n\t}\n\tif cronjob.Spec.Schedule.DayOfWeek != nil {\n\t\tparts[4] = string(*cronjob.Spec.Schedule.DayOfWeek)\n\t}\n\n\t// Join parts to form the full cron expression\n\tcronExpression := strings.Join(parts, \" \")\n\n\treturn validateScheduleFormat(\n\t\tcronExpression,\n\t\tfield.NewPath(\"spec\").Child(\"schedule\"))\n}\n\nfunc validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error {\n\tif _, err := cron.ParseStandard(schedule); err != nil {\n\t\treturn field.Invalid(fldPath, schedule, \"invalid cron schedule format: \"+err.Error())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/cronjob_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tbatchv2 \"tutorial.kubebuilder.io/project/api/v2\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"CronJob Webhook\", func() {\n\tvar (\n\t\tobj       *batchv2.CronJob\n\t\toldObj    *batchv2.CronJob\n\t\tvalidator CronJobCustomValidator\n\t\tdefaulter CronJobCustomDefaulter\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &batchv2.CronJob{}\n\t\toldObj = &batchv2.CronJob{}\n\t\tvalidator = CronJobCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tdefaulter = CronJobCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating CronJob under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating CronJob under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/internal/webhook/v2/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tbatchv2 \"tutorial.kubebuilder.io/project/api/v2\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = batchv2.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCronJobWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"tutorial.kubebuilder.io/project/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n\t// shouldCleanupPrometheus tracks whether Prometheus was installed by this suite.\n\tshouldCleanupPrometheus = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"Ensure that Prometheus is enabled\")\n\t_ = utils.UncommentCode(\"config/default/kustomization.yaml\", \"#- ../prometheus\", \"#\")\n\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n\tBy(\"checking if Prometheus is already installed\")\n\tif !utils.IsPrometheusCRDsInstalled() {\n\t\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\t\tshouldCleanupPrometheus = true\n\n\t\tBy(\"installing Prometheus Operator\")\n\t\tExpect(utils.InstallPrometheusOperator()).To(Succeed(), \"Failed to install Prometheus Operator\")\n\t}\n\n})\n\nvar _ = AfterSuite(func() {\n\t// Teardown Prometheus if it was installed by this suite\n\tif shouldCleanupPrometheus {\n\t\tBy(\"uninstalling Prometheus Operator\")\n\t\tutils.UninstallPrometheusOperator()\n\t}\n\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"tutorial.kubebuilder.io/project/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tBy(\"Cleaning up test CronJob resources\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", \"config/samples/batch_v1_cronjob.yaml\", \"-n\", namespace, \"--ignore-not-found=true\")\n\t\t_, _ = utils.Run(cmd)\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"-f\", \"config/samples/batch_v2_cronjob.yaml\", \"-n\", namespace, \"--ignore-not-found=true\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"validating that the ServiceMonitor for Prometheus is applied in the namespace\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"ServiceMonitor\", \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"ServiceMonitor should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\t\t\"-l\", \"kubernetes.io/service-name=project-webhook-service\",\n\t\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t\t}\n\t\t\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the mutating webhook server is ready\")\n\t\t\tverifyMutatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"MutatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Mutating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyMutatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the validating webhook server is ready\")\n\t\t\tverifyValidatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"ValidatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Validating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyValidatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should provisioned cert-manager\", func() {\n\t\t\tBy(\"validating that cert-manager has the certificate Secret\")\n\t\t\tverifyCertManager := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t\t\t_, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t\tEventually(verifyCertManager).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for mutating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for mutating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tmwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for validating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for validating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for CronJob conversion webhook\", func() {\n\t\t\tBy(\"checking CA injection for CronJob conversion webhook\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"customresourcedefinitions.apiextensions.k8s.io\",\n\t\t\t\t\t\"cronjobs.batch.tutorial.kubebuilder.io\",\n\t\t\t\t\t\"-o\", \"go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t\tIt(\"should successfully convert between v1 and v2 versions\", func() {\n\t\t\tBy(\"waiting for the webhook service to be ready\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpoints\", \"-n\", namespace, \n\t\t\t\t\t\"-l\", \"control-plane=controller-manager\", \n\t\t\t\t\t\"-o\", \"jsonpath={.items[0].subsets[0].addresses[0].ip}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get webhook service endpoints\")\n\t\t\t\tg.Expect(strings.TrimSpace(output)).NotTo(BeEmpty(), \"Webhook endpoint should have an IP\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"creating a v1 CronJob with a specific schedule\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", \"config/samples/batch_v1_cronjob.yaml\", \"-n\", namespace)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create v1 CronJob\")\n\n\t\tBy(\"waiting for the v1 CronJob to be created\")\n\t\tEventually(func(g Gomega) {\n\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.batch.tutorial.kubebuilder.io\", \"cronjob-sample\", \"-n\", namespace)\n\t\t\toutput, err := utils.Run(cmd)\n\t\t\tif err != nil {\n\t\t\t\t// Log controller logs on failure for debugging\n\t\t\t\tlogCmd := exec.Command(\"kubectl\", \"logs\", \"-l\", \"control-plane=controller-manager\", \"-n\", namespace, \"--tail=50\")\n\t\t\t\tlogs, _ := utils.Run(logCmd)\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs when CronJob not found:\\n%s\\n\", logs)\n\t\t\t}\n\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"v1 CronJob should exist, output: \"+output)\n\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\tBy(\"fetching the v1 CronJob and verifying the schedule format\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"cronjob.v1.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule}\")\n\t\t\tv1Schedule, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to get v1 CronJob schedule\")\n\t\t\tExpect(strings.TrimSpace(v1Schedule)).To(Equal(\"*/1 * * * *\"),\n\t\t\t\t\"v1 schedule should be in cron format\")\n\n\t\t\tBy(\"fetching the same CronJob as v2 and verifying the converted schedule\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v2.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule.minute}\")\n\t\t\t\tv2Minute, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get v2 CronJob schedule\")\n\t\t\t\tg.Expect(strings.TrimSpace(v2Minute)).To(Equal(\"*/1\"),\n\t\t\t\t\t\"v2 schedule.minute should be converted from v1 schedule\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"creating a v2 CronJob with structured schedule fields\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"apply\", \"-f\", \"config/samples/batch_v2_cronjob.yaml\", \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create v2 CronJob\")\n\n\t\t\tBy(\"verifying the v2 CronJob has the correct structured schedule\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v2.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule.minute}\")\n\t\t\t\tv2Minute, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get v2 CronJob schedule\")\n\t\t\t\tg.Expect(strings.TrimSpace(v2Minute)).To(Equal(\"*/1\"),\n\t\t\t\t\t\"v2 CronJob should have minute field set\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"fetching the v2 CronJob as v1 and verifying schedule conversion\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v1.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule}\")\n\t\t\t\tv1Schedule, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get converted v1 schedule\")\n\t\t\t\t// When v2 only has minute field set, it converts to \"*/1 * * * *\"\n\t\t\t\tg.Expect(strings.TrimSpace(v1Schedule)).To(Equal(\"*/1 * * * *\"),\n\t\t\t\t\t\"v1 schedule should be converted from v2 structured schedule\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\t\t})\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/testdata/project/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n\n\tprometheusOperatorVersion = \"v0.89.0\"\n\tprometheusOperatorURL     = \"https://github.com/prometheus-operator/prometheus-operator/\" +\n\t\t\"releases/download/%s/bundle.yaml\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.\nfunc InstallPrometheusOperator() error {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"create\", \"-f\", url)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// UninstallPrometheusOperator uninstalls the prometheus\nfunc UninstallPrometheusOperator() {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed\n// by verifying the existence of key CRDs related to Prometheus.\nfunc IsPrometheusCRDsInstalled() bool {\n\t// List of common Prometheus CRDs\n\tprometheusCRDs := []string{\n\t\t\"prometheuses.monitoring.coreos.com\",\n\t\t\"prometheusrules.monitoring.coreos.com\",\n\t\t\"prometheusagents.monitoring.coreos.com\",\n\t}\n\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"custom-columns=NAME:.metadata.name\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range prometheusCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/tutorial.md",
    "content": "# Tutorial: Multi-Version API\n\nMost projects start out with an alpha API that changes release to release.\nHowever, eventually, most projects will need to move to a more stable API.\nOnce your API is stable though, you can't make breaking changes to it.\nThat's where API versions come into play.\n\nLet's make some changes to the `CronJob` API spec and make sure all the\ndifferent versions are supported by our CronJob project.\n\nIf you haven't already, make sure you've gone through the base [CronJob\nTutorial](/cronjob-tutorial/cronjob-tutorial.md).\n\n<aside class=\"note\">\n\n<h1>Following Along vs Jumping Ahead</h1>\n\nNote that most of this tutorial is generated from literate Go files that\nform a runnable project, and live in the book source directory:\n[docs/book/src/multiversion-tutorial/testdata/project][tutorial-source].\n\n[tutorial-source]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/multiversion-tutorial/testdata/project\n\n</aside>\n\nNext, let's figure out what changes we want to make...\n"
  },
  {
    "path": "docs/book/src/multiversion-tutorial/webhooks.md",
    "content": "# Setting up the webhooks\n\nOur conversion is in place, so all that's left is to tell\ncontroller-runtime about our conversion.\n\n## Webhook setup for v1...\n\nThe v1 webhook handles conversion (as the hub) and provides validation/defaulting\nfor the v1 CronJob format with a string-based schedule:\n\n{{#literatego ./testdata/project/internal/webhook/v1/cronjob_webhook.go}}\n\n## Webhook setup for v2...\n\nThe v2 webhook provides validation and defaulting for the v2 CronJob format\nwith the structured CronSchedule type. Note how the validation logic differs\nfrom v1 - it builds a cron expression from the individual schedule fields:\n\n{{#literatego ./testdata/project/internal/webhook/v2/cronjob_webhook.go}}\n\n## ...and `main.go`\n\nSimilarly, our existing main file is sufficient:\n\n{{#literatego ./testdata/project/cmd/main.go}}\n\nEverything's set up and ready to go!  All that's left now is to test out\nour webhooks.\n"
  },
  {
    "path": "docs/book/src/plugins/available/autoupdate-v1-alpha.md",
    "content": "# AutoUpdate (`autoupdate/v1-alpha`)\n\nKeeping your Kubebuilder project up to date with the latest improvements shouldn’t be a chore.\nWith a small amount of setup, you can receive **automatic Pull Request** suggestions whenever a new\nKubebuilder release is available — keeping your project **maintained, secure, and aligned with ecosystem changes**.\n\nThis automation uses the [`kubebuilder alpha update`][alpha-update-command] command with a **3-way merge strategy** to\nrefresh your project scaffold, and wraps it in a GitHub Actions workflow that opens an **Issue** with a **Pull Request compare link** so you can create the PR and review it.\n\n<aside class=\"warning\">\n<h3>Protect your branches</h3>\n\nThis workflow by default **only** creates and pushes the merged files to a branch\ncalled `kubebuilder-update-from-<from-version>-to-<to-version>`.\n\nTo keep your codebase safe, use branch protection rules to ensure that\nchanges aren't pushed or merged without proper review.\n\n</aside>\n\n## When to Use It\n\n\n- When you want to reduce the burden of keeping the project updated and well-maintained.\n- When you want guidance and help from AI to know what changes are needed to keep your project up to date and to solve conflicts (requires `--use-gh-models` flag and GitHub Models permissions).\n\n## How to Use It\n\n- If you want to add the `autoupdate` plugin to your project:\n\n```shell\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\"\n```\n\n- If you want to create a new project with the `autoupdate` plugin:\n\n```shell\nkubebuilder init --plugins=go/v4,autoupdate/v1-alpha\n```\n\n### Optional: GitHub Models AI Summary\n\nBy default, the workflow works without GitHub Models to avoid permission errors.\nIf you want AI-generated summaries in your update issues:\n\n```shell\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models\n```\n\n<aside class=\"note\">\n<h1>Permissions required to use GitHub Models in GitHub Actions</h1>\n\nTo use GitHub Models in your workflows, organization and repository administrators must grant this permission.\n\n**If you have admin access:**\n\n1. Go to **Settings → Code and automation → Models**\n2. Enable GitHub Models for your repository\n\n**Don't see the Models option?**\n\nYour organization or enterprise may have disabled it. Contact your administrator:\n\n- Organization admins: [Managing Models in your organization][manage-org-models]\n- Enterprise admins: [Managing Models at enterprise scale][manage-models-at-scale]\n\n</aside>\n\n## How It Works\n\nThe plugin scaffolds a GitHub Actions workflow that checks for new Kubebuilder releases every week. When an update is available, it:\n\n1. Creates a new branch with the merged changes\n2. Opens a GitHub Issue with a PR compare link\n\n**Example Issue:**\n\n<img width=\"638\" height=\"482\" alt=\"Example Issue\" src=\"https://github.com/user-attachments/assets/589fd16b-7709-4cd5-b169-fd53d69790d4\" />\n\n**With GitHub Models enabled** (optional), you also get AI-generated summaries:\n\n<img width=\"582\" height=\"646\" alt=\"AI Summary\" src=\"https://github.com/user-attachments/assets/d460a5af-5ca4-4dd5-afb8-7330dd6de148\" />\n\n**Conflict help** (when needed):\n\n<img width=\"600\" height=\"188\" alt=\"Conflicts\" src=\"https://github.com/user-attachments/assets/2142887a-730c-499a-94df-c717f09ab600\" />\n\n## Customizing the Workflow\n\nThe generated workflow uses the `kubebuilder alpha update` command with default flags. You can customize the workflow by editing `.github/workflows/auto_update.yml` to add additional flags:\n\n**Default flags used:**\n- `--force` - Continue even if conflicts occur (automation-friendly)\n- `--push` - Automatically push the output branch to remote\n- `--restore-path .github/workflows` - Preserve CI workflows from base branch\n- `--open-gh-issue` - Create a GitHub Issue with PR compare link\n- `--use-gh-models` - (optional) Add AI summary to the issue\n\n**Additional available flags:**\n- `--merge-message` - Custom commit message for clean merges\n- `--conflict-message` - Custom commit message when conflicts occur\n- `--from-version` - Specify the version to upgrade from\n- `--to-version` - Specify the version to upgrade to\n- `--output-branch` - Custom output branch name\n- `--show-commits` - Keep full history instead of squashing\n- `--git-config` - Pass per-invocation Git config\n\nFor complete documentation on all available flags, see the [`kubebuilder alpha update`][alpha-update-command] reference.\n\n**Example: Customize commit messages**\n\nEdit `.github/workflows/auto_update.yml`:\n\n```yaml\n- name: Run kubebuilder alpha update\n  run: |\n    kubebuilder alpha update \\\n      --force \\\n      --push \\\n      --restore-path .github/workflows \\\n      --open-gh-issue \\\n      --merge-message \"chore: update kubebuilder scaffold\" \\\n      --conflict-message \"chore: update with conflicts - review needed\"\n```\n\n## Troubleshooting\n\n#### If you get the 403 Forbidden Error\n\n**Error message:**\n```\nERROR Update failed error=failed to open GitHub issue: gh models run failed: exit status 1\nError: unexpected response from the server: 403 Forbidden\n```\n\n**Quick fix:** Disable GitHub Models (works for everyone)\n\n```shell\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\"\n```\n\nThis regenerates the workflow without GitHub Models:\n\n```yaml\npermissions:\n  contents: write\n  issues: write\n  # No models: read permission\n\nsteps:\n  - name: Checkout repository\n    uses: actions/checkout@v4\n    # ... other setup steps\n\n  - name: Run kubebuilder alpha update\n    # WARNING: This workflow does not use GitHub Models AI summary by default.\n    # To enable AI-generated summaries, you need permissions to use GitHub Models.\n    # If you have the required permissions, re-run:\n    #   kubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models\n    run: |\n      kubebuilder alpha update \\\n        --force \\\n        --push \\\n        --restore-path .github/workflows \\\n        --open-gh-issue\n```\n\nThe workflow continues to work—just without AI summaries.\n\n**To enable GitHub Models instead:**\n\n1. Ask your GitHub administrator to enable Models (see links below)\n2. Enable it in **Settings → Code and automation → Models**\n3. Re-run with:\n\n```shell\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models\n```\n\nThis regenerates the workflow WITH GitHub Models:\n\n```yaml\npermissions:\n  contents: write\n  issues: write\n  models: read  # Added for GitHub Models\n\nsteps:\n  - name: Checkout repository\n    uses: actions/checkout@v4\n    # ... other setup steps\n\n  - name: Install gh-models extension\n    run: |\n      gh extension install github/gh-models --force\n      gh models --help >/dev/null\n\n  - name: Run kubebuilder alpha update\n    # --use-gh-models: Adds an AI-generated comment to the Issue with\n    #   a summary of scaffold changes and conflict-resolution guidance (if any).\n    run: |\n      kubebuilder alpha update \\\n        --force \\\n        --push \\\n        --restore-path .github/workflows \\\n        --open-gh-issue \\\n        --use-gh-models\n```\n\n## Demonstration\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/dHNKx5jPSqc?si=wYwZZ0QLwFij10Sb\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n\n[alpha-update-command]: ./../../reference/commands/alpha_update.md\n[ai-models]: https://docs.github.com/en/github-models/about-github-models\n[manage-models-at-scale]: https://docs.github.com/en/github-models/github-models-at-scale/manage-models-at-scale\n[manage-org-models]: https://docs.github.com/en/organizations/managing-organization-settings/managing-or-restricting-github-models-for-your-organization\n"
  },
  {
    "path": "docs/book/src/plugins/available/deploy-image-plugin-v1-alpha.md",
    "content": "# Deploy Image Plugin (deploy-image/v1-alpha)\n\nThe `deploy-image` plugin allows users to create [controllers][controller-runtime] and custom resources that deploy and manage container images on the cluster, following Kubernetes best practices. It simplifies the complexities of deploying images while allowing users to customize their projects as needed.\n\nBy using this plugin, you will get:\n\n- A controller implementation to deploy and manage an Operand (image) on the cluster.\n- Tests to verify the reconciliation logic, using [ENVTEST][envtest].\n- Custom resource samples updated with the necessary specifications.\n- Environment variable support for managing the Operand (image) within the manager.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nSee the `project-v4-with-plugins` directory under the [testdata][testdata]\ndirectory in the Kubebuilder project to check an example\nof scaffolding created using this plugin.\n\nThe `Memcached` API and its controller was scaffolded\nusing the command:\n\n```shell\nkubebuilder create api \\\n  --group example.com \\\n  --version v1alpha1 \\\n  --kind Memcached \\\n  --image=memcached:memcached:1.6.26-alpine3.19 \\\n  --image-container-command=\"memcached,--memory-limit=64,-o,modern,-v\" \\\n  --image-container-port=\"11211\" \\\n  --run-as-user=\"1001\" \\\n  --plugins=\"deploy-image/v1-alpha\"\n```\n\nThe `Busybox` API was created with:\n\n```shell\nkubebuilder create api \\\n  --group example.com \\\n  --version v1alpha1 \\\n  --kind Busybox \\\n  --image=busybox:1.36.1 \\\n  --plugins=\"deploy-image/v1-alpha\"\n```\n</aside>\n\n\n## When to use it?\n\n- This plugin is ideal for users who are just getting started with Kubernetes operators.\n- It helps users deploy and manage an image (Operand) using the [Operator pattern][operator-pattern].\n- If you're looking for a quick and efficient way to set up a custom controller and manage a container image, this plugin is a great choice.\n\n## How to use it?\n\n1. **Initialize your project**:\n   After creating a new project with `kubebuilder init`, you can use this\n   plugin to create APIs. Ensure that you've completed the\n   [quick start][quick-start] guide before proceeding.\n\n2. **Create APIs**:\n   With this plugin, you can [create APIs][create-apis] to specify the image (Operand) you want to deploy on the cluster. You can also optionally specify the command, port, and security context using various flags:\n\n   Example command:\n   ```sh\n   kubebuilder create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.6.15-alpine --image-container-command=\"memcached,--memory-limit=64,modern,-v\" --image-container-port=\"11211\" --run-as-user=\"1001\" --plugins=\"deploy-image/v1-alpha\"\n   ```\n\n<aside class=\"warning\">\n<h1>Note on make run:</h1>\n\nWhen running the project locally with `make run`, the Operand image\nprovided will be stored as an environment variable in the\n`config/manager/manager.yaml` file.\n\nEnsure you export the environment variable before running the project locally, such as:\n\n```shell\nexport MEMCACHED_IMAGE=\"memcached:1.4.36-alpine\"\n```\n\n</aside>\n\n## Subcommands\n\nThe `deploy-image` plugin includes the following subcommand:\n\n- `create api`: Use this command to scaffold the API and controller code to manage the container image.\n\n## Affected files\n\nWhen using the `create api` command with this plugin, the following\nfiles are affected, in addition to the existing Kubebuilder scaffolding:\n\n- `controllers/*_controller_test.go`: Scaffolds tests for the controller.\n- `controllers/*_suite_test.go`: Scaffolds or updates the test suite.\n- `api/<version>/*_types.go`: Scaffolds the API specs.\n- `config/samples/*_.yaml`: Scaffolds default values for the custom resource.\n- `main.go`: Updates the file to add the controller setup.\n- `config/manager/manager.yaml`: Updates to include environment variables for storing the image.\n\n## Further Resources:\n\n- Check out this [video][video] to see how it works.\n\n[video]: https://youtu.be/UwPuRjjnMjY\n[operator-pattern]: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins\n[envtest]: ./../../reference/envtest.md\n[quick-start]: ./../../quick-start.md\n[create-apis]: ../../cronjob-tutorial/new-api.md"
  },
  {
    "path": "docs/book/src/plugins/available/go-v4-plugin.md",
    "content": "# go/v4 (go.kubebuilder.io/v4)\n\n**(Default Scaffold)**\n\nKubebuilder will scaffold using the `go/v4` plugin only if specified when initializing the project.\nThis plugin is a composition of the `kustomize.common.kubebuilder.io/v2` and `base.go.kubebuilder.io/v4` plugins\nusing the [Bundle Plugin][bundle]. It scaffolds a project template\nthat helps in constructing sets of [controllers][controller-runtime].\n\nBy following the [quickstart][quickstart] and creating any project,\nyou will be using this plugin by default.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can check samples using this plugin by looking at the `project-v4-<options>` projects under the [testdata][testdata]\ndirectory on the root directory of the Kubebuilder project.\n\n</aside>\n\n## How to use it ?\n\nTo create a new project with the `go/v4` plugin the following command can be used:\n\n```sh\nkubebuilder init --domain tutorial.kubebuilder.io --repo tutorial.kubebuilder.io/project --plugins=go/v4\n```\n\n## Subcommands supported by the plugin\n\n-  Init -  `kubebuilder init [OPTIONS]`\n-  Edit -  `kubebuilder edit [OPTIONS]`\n-  Create API -  `kubebuilder create api [OPTIONS]`\n-  Create Webhook - `kubebuilder create webhook [OPTIONS]`\n\n## Further resources\n\n- To see the composition of plugins, you can check the source code for the Kubebuilder [main.go][plugins-main].\n- Check the code implementation of the [base Golang plugin `base.go.kubebuilder.io/v4`][v4-plugin].\n- Check the code implementation of the [Kustomize/v2 plugin][kustomize-plugin].\n- Check [controller-runtime][controller-runtime] to know more about controllers.\n\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[quickstart]: ./../../quick-start.md\n[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata\n[plugins-main]: ./../../../../../cmd/main.go\n[kustomize-plugin]: ./../../plugins/available/kustomize-v2.md\n[kustomize]: https://github.com/kubernetes-sigs/kustomize\n[standard-go-project]: https://github.com/golang-standards/project-layout\n[v4-plugin]: ./../../../../../pkg/plugins/golang/v4\n[migration-guide-doc]: ./../../migration/migration_guide_gov3_to_gov4.md\n[project-doc]: ./../../reference/project-config.md\n[bundle]: ./../../../../../pkg/plugin/bundle.go\n"
  },
  {
    "path": "docs/book/src/plugins/available/grafana-v1-alpha.md",
    "content": "# Grafana Plugin (`grafana/v1-alpha`)\n\nThe Grafana plugin is an optional plugin that can be used to\nscaffold Grafana Dashboards to allow you to check out the\ndefault metrics which are exported by projects\nusing [controller-runtime][controller-runtime].\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can check its default scaffold by looking at the `project-v4-with-plugins` projects\nunder the [testdata][testdata] directory on the root directory of the Kubebuilder project.\n\n</aside>\n\n## When to use it ?\n\n- If you are looking to observe the metrics\nexported by [controller metrics][controller-metrics] and\ncollected by Prometheus via [Grafana][grafana].\n\n## How to use it ?\n\n### Prerequisites:\n\n- Your project must be using [controller-runtime][controller-runtime] to expose the metrics via the [controller default metrics][controller-metrics] and they need to be collected by Prometheus.\n- Access to [Prometheus][prometheus].\n  - Prometheus should have an endpoint exposed. (For `prometheus-operator`, this is similar as: http://prometheus-k8s.monitoring.svc:9090 )\n  - The endpoint is ready to/already become the datasource of your Grafana. See [Add a data source](https://grafana.com/docs/grafana/latest/datasources/add-a-data-source/)\n- Access to [Grafana][grafana-install]. Make sure you have:\n  - [Dashboard edit permission][grafana-permissions]\n  - Prometheus Data source\n    ![pre][prometheus-data-source]\n\n<aside class=\"note\">\n\nCheck the [metrics][reference-metrics-doc] to know how to enable the metrics for your projects scaffold with Kubebuilder.\n\nSee that in the [config/prometheus][kustomize-plugin] you will find the ServiceMonitor to enable the metrics in the default endpoint `/metrics`.\n\n</aside>\n\n### Basic Usage\n\nThe Grafana plugin is attached to the `init` subcommand and the `edit` subcommand:\n\n```sh\n# Initialize a new project with grafana plugin\nkubebuilder init --plugins grafana.kubebuilder.io/v1-alpha\n\n# Enable grafana plugin to an existing project\nkubebuilder edit --plugins grafana.kubebuilder.io/v1-alpha\n```\n\nThe plugin will create a new directory and scaffold the JSON files under it (i.e. `grafana/controller-runtime-metrics.json`).\n\n#### Show case:\n\nSee an example of how to use the plugin in your project:\n\n![output](https://user-images.githubusercontent.com/18136486/175382307-9a6c3b8b-6cc7-4339-b221-2539d0fec042.gif)\n\n#### Now, let's check how to use the Grafana dashboards\n\n1. Copy the JSON file\n2. Visit `<your-grafana-url>/dashboard/import` to [import a new dashboard](https://grafana.com/docs/grafana/latest/dashboards/export-import/#import-dashboard).\n3. Paste the JSON content to `Import via panel json`, then press `Load` button\n   <img width=\"644\" src=\"https://user-images.githubusercontent.com/18136486/176121955-1c4aec9c-0ba4-4271-9767-e8d1726d9d9a.png\">\n4. Select the data source for Prometheus metrics\n   <img width=\"633\" src=\"https://user-images.githubusercontent.com/18136486/176122261-e3eab5b0-9fc4-45fc-a68c-d9ce1cfe96ee.png\">\n5. Once the json is imported in Grafana, the dashboard is ready.\n\n### Grafana Dashboard\n\n#### Controller Runtime Reconciliation total & errors\n\n- Metrics:\n  - controller_runtime_reconcile_total\n  - controller_runtime_reconcile_errors_total\n- Query:\n  - sum(rate(controller_runtime_reconcile_total{job=\"$job\"}[5m])) by (instance, pod)\n  - sum(rate(controller_runtime_reconcile_errors_total{job=\"$job\"}[5m])) by (instance, pod)\n- Description:\n  - Per-second rate of total reconciliation as measured over the last 5 minutes\n  - Per-second rate of reconciliation errors as measured over the last 5 minutes\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/176122555-f3493658-6c99-4ad6-a9b7-63d85620d370.png\">\n\n#### Controller CPU & Memory Usage\n\n- Metrics:\n  - process_cpu_seconds_total\n  - process_resident_memory_bytes\n- Query:\n  - rate(process_cpu_seconds_total{job=\"$job\", namespace=\"$namespace\", pod=\"$pod\"}[5m]) \\* 100\n  - process_resident_memory_bytes{job=\"$job\", namespace=\"$namespace\", pod=\"$pod\"}\n- Description:\n  - Per-second rate of CPU usage as measured over the last 5 minutes\n  - Allocated Memory for the running controller\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/177239808-7d94b17d-692c-4166-8875-6d9332e05bcb.png\">\n\n#### Seconds of P50/90/99 Items Stay in Work Queue\n\n- Metrics\n  - workqueue_queue_duration_seconds_bucket\n- Query:\n  - histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{job=\"$job\", namespace=\"$namespace\"}[5m])) by (instance, name, le))\n- Description\n  - Seconds an item stays in workqueue before being requested.\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/180359126-452b2a0f-a511-4ae3-844f-231d13cd27f8.png\">\n\n#### Seconds of P50/90/99 Items Processed in Work Queue\n\n- Metrics\n  - workqueue_work_duration_seconds_bucket\n- Query:\n  - histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{job=\"$job\", namespace=\"$namespace\"}[5m])) by (instance, name, le))\n- Description\n  - Seconds of processing an item from workqueue takes.\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/180359617-b7a59552-1e40-44f9-999f-4feb2584b2dd.png\">\n\n#### Add Rate in Work Queue\n\n- Metrics\n  - workqueue_adds_total\n- Query:\n  - sum(rate(workqueue_adds_total{job=\"$job\", namespace=\"$namespace\"}[5m])) by (instance, name)\n- Description\n  - Per-second rate of items added to work queue\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/180360073-698b6f77-a2c4-4a95-8313-fd8745ad472f.png\">\n\n#### Retries Rate in Work Queue\n\n- Metrics\n  - workqueue_retries_total\n- Query:\n  - sum(rate(workqueue_retries_total{job=\"$job\", namespace=\"$namespace\"}[5m])) by (instance, name)\n- Description\n  - Per-second rate of retries handled by workqueue\n- Sample: <img width=\"912\" src=\"https://user-images.githubusercontent.com/18136486/180360101-411c81e9-d54e-4b21-bbb0-e3f94fcf48cb.png\">\n\n#### Number of Workers in Use\n\n- Metrics\n  - controller_runtime_active_workers\n- Query:\n  - controller_runtime_active_workers{job=\"$job\", namespace=\"$namespace\"}\n- Description\n  - The number of active controller workers\n- Sample: <img width=\"912\" src=\"https://github.com/kubernetes-sigs/kubebuilder/assets/18136486/288db1b5-e2d8-48ea-9aae-30de7eeca277\">\n\n#### WorkQueue Depth\n\n- Metrics\n  - workqueue_depth\n- Query:\n  - workqueue_depth{job=\"$job\", namespace=\"$namespace\"}\n- Description\n  - Current depth of workqueue\n- Sample: <img width=\"912\" src=\"https://github.com/kubernetes-sigs/kubebuilder/assets/18136486/34f14df4-0428-460e-9658-01dd3d34aade\">\n\n#### Unfinished Seconds\n\n- Metrics\n  - workqueue_unfinished_work_seconds\n- Query:\n  - rate(workqueue_unfinished_work_seconds{job=\"$job\", namespace=\"$namespace\"}[5m])\n- Description\n  - How many seconds of work has done that is in progress and hasn't been observed by work_duration.\n- Sample: <img width=\"912\" src=\"https://github.com/kubernetes-sigs/kubebuilder/assets/18136486/081727c0-9531-4f7a-9649-87723ebc773f\">\n\n### Visualize Custom Metrics\n\nThe Grafana plugin supports scaffolding manifests for custom metrics.\n\n#### Generate Config Template\n\nWhen the plugin is triggered for the first time, `grafana/custom-metrics/config.yaml` is generated.\n\n```yaml\n---\ncustomMetrics:\n#  - metric: # Raw custom metric (required)\n#    type:   # Metric type: counter/gauge/histogram (required)\n#    expr:   # Prom_ql for the metric (optional)\n#    unit:   # Unit of measurement, examples: s,none,bytes,percent,etc. (optional)\n```\n\n#### Add Custom Metrics to Config\n\nYou can enter multiple custom metrics in the file. For each element, you need to specify the `metric` and its `type`.\nThe Grafana plugin can automatically generate `expr` for visualization.\nAlternatively, you can provide `expr` and the plugin will use the specified one directly.\n\n```yaml\n---\ncustomMetrics:\n  - metric: memcached_operator_reconcile_total # Raw custom metric (required)\n    type: counter # Metric type: counter/gauge/histogram (required)\n    unit: none\n  - metric: memcached_operator_reconcile_time_seconds_bucket\n    type: histogram\n```\n\n#### Scaffold Manifest\n\nOnce `config.yaml` is configured, you can run `kubebuilder edit --plugins grafana.kubebuilder.io/v1-alpha` again.\nThis time, the plugin will generate `grafana/custom-metrics/custom-metrics-dashboard.json`, which can be imported to Grafana UI.\n\n#### Show case:\n\nSee an example of how to visualize your custom metrics:\n\n![output2][show-case]\n\n## Subcommands\n\nThe Grafana plugin implements the following subcommands:\n\n- edit (`$ kubebuilder edit [OPTIONS]`)\n\n- init (`$ kubebuilder init [OPTIONS]`)\n\n## Affected files\n\nThe following scaffolds will be created or updated by this plugin:\n\n- `grafana/*.json`\n\n## Further resources\n\n- Check out [video to show how it works][video]\n- Checkout the [video to show how the custom metrics feature works][video-custom-metrics]\n- Refer to a sample of `serviceMonitor` provided by [kustomize plugin][kustomize-plugin]\n- Check the [plugin implementation][plugin-implementation]\n- [Grafana Docs][grafana-docs] of importing JSON file\n- The usage of serviceMonitor by [Prometheus Operator][servicemonitor]\n\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[grafana]: https://grafana.com/docs/grafana/next/\n[grafana-docs]: https://grafana.com/docs/grafana/latest/dashboards/export-import/#import-dashboard\n[kube-prometheus]: https://github.com/prometheus-operator/kube-prometheus\n[prometheus]: https://prometheus.io/docs/introduction/overview/\n[prom-operator]: https://prometheus-operator.dev/docs/prologue/introduction/\n[servicemonitor]: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources\n[grafana-install]: https://grafana.com/docs/grafana/latest/setup-grafana/installation/\n[grafana-permissions]: https://grafana.com/docs/grafana/next/administration/roles-and-permissions/#dashboard-permissions\n[prometheus-data-source]: https://user-images.githubusercontent.com/18136486/176119794-f6d69b0b-93f0-4f9e-a53c-daf9f77dadae.gif\n[video]: https://youtu.be/-w_JjcV8jXc\n[video-custom-metrics]: https://youtu.be/x_0FHta2HXc\n[show-case]: https://user-images.githubusercontent.com/18136486/186933170-d2e0de71-e079-4d1b-906a-99a549d66ebf.gif\n[controller-metrics]: ./../../reference/metrics-reference.md\n[kustomize-plugin]: ./../../../../../testdata/project-v4-with-plugins/config/prometheus/monitor.yaml\n[plugin-implementation]: ./../../../../../pkg/plugins/optional/grafana/\n[reference-metrics-doc]: ./../../reference/metrics.md#exporting-metrics-for-prometheus\n[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins\n\n"
  },
  {
    "path": "docs/book/src/plugins/available/helm-v1-alpha.md",
    "content": "# Helm Plugin (`helm/v1-alpha`) - **DEPRECATED**\n\n<aside class=\"warning\">\n<h1> Deprecated Plugin</h1>\n\nThe `helm/v1-alpha` plugin is **deprecated**. Please use [`helm/v2-alpha`](./helm-v2-alpha.md) instead.\n\nThe v2-alpha version provides:\n- Dynamic Helm chart generation from kustomize output\n- Better preservation of customizations (env vars, labels, annotations)\n- Organized template structure matching your config/ directory\n- More flexible configuration options\n\n</aside>\n\nThe Helm plugin is an optional plugin that can be used to scaffold a Helm chart, allowing you to distribute the project using Helm.\n\nBy default, users can generate a bundle with all the manifests by running the following command:\n\n```bash\nmake build-installer IMG=<some-registry>/<project-name:tag>\n```\n\nThis allows the project consumer to install the solution by applying the bundle with:\n\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/project-v4/<tag or branch>/dist/install.yaml\n```\n\nHowever, in many scenarios, you might prefer to provide a Helm chart to package your solution.\nIf so, you can use this plugin to generate the Helm chart under the `dist` directory.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can check the plugin usage by looking at `project-v4-with-plugins` samples\nunder the [testdata][testdata] directory on the root directory of the Kubebuilder project.\n\n</aside>\n\n## When to use it\n\n- If you want to provide a Helm chart for users to install and manage your project.\n- If you need to update the Helm chart generated under `dist/chart/` with the latest project changes:\n  - After generating new manifests, use the `edit` option to sync the Helm chart.\n  - **IMPORTANT:** If you have created a webhook or an API using the [DeployImage][deployImage-plugin] plugin,\n  you must run the `edit` command with the `--force` flag to regenerate the Helm chart values based\n  on the latest manifests (_after running `make manifests`_) to ensure that the HelmChart values are\n  updated accordingly. In this case, if you have customized the files\n  under `dist/chart/values.yaml`, and the `templates/manager/manager.yaml`, you will need to manually reapply your customizations on top\n  of the latest changes after regenerating the Helm chart.\n\n<aside class=\"note\">\n<H1> Why CRDs are added under templates? </H1>\n\nAlthough [Helm best practices](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you) recommend placing CRDs under a top-level `crds/` directory, the Kubebuilder Helm plugin intentionally places them under `templates/crd`.\n\nThe rationale is tied to how Helm itself handles CRDs.\nBy default, Helm will install CRDs once during the initial release,\nbut it will **ignore CRD changes** on subsequent upgrades.\n\nThis can lead to surprising behavior where chart upgrades silently\nskip CRD updates, leaving clusters out of sync.\n\nTo avoid endorsing this behavior, the Kubebuilder plugin follows the approach of packaging\nCRDs inside `templates/`. In this mode, Helm treats CRDs like\nany other resource, ensuring they are applied and upgraded as expected.\nWhile this prevents mixing CRDs and CRs of the same type in a single chart (since Helm cannot wait between creation steps), it ensures predictable and explicit lifecycle management of CRDs.\n\nIn short:\n- **Helm `crds/` directory** → one-time install only, no upgrades.\n- **Kubebuilder `templates/crd`** → CRDs managed like other manifests, upgrades included.\n\nThis design choice prioritizes correctness and maintainability over Helm's default convention,\nwhile leaving room for future improvements (such as scaffolding separate charts for APIs and controllers).\n</aside>\n\n## How to use it ?\n\n### Basic Usage\n\nThe Helm plugin is attached to the `edit` subcommand as the `helm/v1-alpha` plugin\nrelies on the Go project being scaffolded first.\n\n```sh\n\n# Initialize a new project\nkubebuilder init\n\n# Enable or Update the helm chart via the helm plugin to an existing project\n# Before run the edit command, run `make manifests` to generate the manifest under `config/`\nmake manifests\nkubebuilder edit --plugins=helm/v1-alpha\n```\n<aside class=\"note\">\n  <h1>Use the edit command to update the Helm Chart with the latest changes</h1>\n\n  After making changes to your project, ensure that you run `make manifests` and then\n  use the command `kubebuilder edit --plugins=helm/v1-alpha` to update the Helm Chart.\n\n  Note that the following files will **not** be updated unless you use the `--force` flag:\n\n  <pre>\n  dist/chart/\n  ├── values.yaml\n  └── templates/\n      └── manager/\n          └── manager.yaml\n  </pre>\n\n  The files `chart/Chart.yaml`, `chart/templates/_helpers.tpl`, and `chart/.helmignore` are never updated\n  after their initial creation unless you remove them.\n\n</aside>\n\n## Subcommands\n\nThe Helm plugin implements the following subcommands:\n\n- edit (`$ kubebuilder edit [OPTIONS]`)\n\n## Affected files\n\nThe following scaffolds will be created or updated by this plugin:\n\n- `dist/chart/*`\n\n[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins\n[deployImage-plugin]: ./deploy-image-plugin-v1-alpha.md"
  },
  {
    "path": "docs/book/src/plugins/available/helm-v2-alpha.md",
    "content": "# Helm Plugin `(helm/v2-alpha)`\n\nThe Helm plugin **v2-alpha** provides a way to package your project as a Helm chart, enabling distribution in Helm’s native format.\nInstead of using static templates, this plugin dynamically generates Helm charts from your project’s **kustomize output** (via `make build-installer`).\nIt keeps your custom settings such as environment variables, labels, annotations, and security contexts.\n\nThis lets you deliver your Kubebuilder project in two ways:\n- As a **bundle** (`dist/install.yaml`) generated with kustomize\n- As a **Helm chart** that matches the same output\n\n## Why Helm?\n\nBy default, you can create a bundle of manifests with:\n\n```shell\nmake build-installer IMG=<registry>/<project-name:tag>\n```\n\nUsers can install it directly:\n\n```shell\nkubectl apply -f https://raw.githubusercontent.com/<org>/project-v4/<tag-or-branch>/dist/install.yaml\n```\nBut many people prefer Helm for packaging, upgrades, and distribution.\nThe **helm/v2-alpha** plugin converts the bundle (`dist/install.yaml`) into a Helm chart that mirrors your project.\n\n## Key Features\n\n- **Dynamic Generation**: Charts are built from real kustomize output, not boilerplate.\n- **Preserves Customizations**: Keeps env vars, labels, annotations, and patches.\n- **Structured Output**: Templates follow your `config/` directory layout.\n- **Smart Values**: `values.yaml` includes only actual configurable parameters.\n- **File Preservation**: `Chart.yaml` is never overwritten. Without `--force`, `values.yaml`, `NOTES.txt`, `_helpers.tpl`, `.helmignore` and `.github/workflows/test-chart.yml` are preserved.\n- **Handles Custom Resources**: Resources not matching standard layout (custom Services, ConfigMaps, etc.) are placed in `templates/extras/` with proper templating.\n\n## When to Use It\n\nUse the **helm/v2-alpha** plugin if:\n- You want Helm charts that stay true to your kustomize setup\n- You need charts that update with your project automatically\n- You want a clean template layout similar to `config/`\n- You want to distribute your solution using either this format\n\n## Usage\n\n### Basic Workflow\n\n```shell\n# Create a new project\nkubebuilder init\n\n# Build the installer bundle\nmake build-installer IMG=<registry>/<project:tag>\n\n# Create Helm chart from kustomize output\nkubebuilder edit --plugins=helm/v2-alpha\n\n# Regenerate preserved files (Chart.yaml never overwritten)\nkubebuilder edit --plugins=helm/v2-alpha --force\n```\n\n### Advanced Options\n\n```shell\n# Use a custom manifests file\nkubebuilder edit --plugins=helm/v2-alpha --manifests=manifests/custom-install.yaml\n\n# Write chart to a custom output directory\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts\n\n# Combine manifests and output\nkubebuilder edit --plugins=helm/v2-alpha \\\n  --manifests=manifests/install.yaml \\\n  --output-dir=helm-charts\n```\n\n## Chart Structure\n\nThe plugin creates a chart layout that matches your `config/`:\n\n```shell\n<output-dir>/chart/\n├── Chart.yaml\n├── values.yaml\n├── .helmignore\n└── templates/\n    ├── NOTES.txt\n    ├── _helpers.tpl\n    ├── rbac/                    # Individual RBAC files (examples)\n    │   ├── controller-manager.yaml\n    │   ├── leader-election-role.yaml\n    │   ├── leader-election-rolebinding.yaml\n    │   ├── manager-role.yaml\n    │   ├── manager-rolebinding.yaml\n    │   ├── metrics-auth-role.yaml\n    │   ├── metrics-auth-rolebinding.yaml\n    │   ├── metrics-reader.yaml\n    │   ├── memcached-admin-role.yaml\n    │   ├── memcached-editor-role.yaml\n    │   ├── memcached-viewer-role.yaml\n    │   ├── busybox-admin-role.yaml\n    │   ├── busybox-editor-role.yaml\n    │   ├── busybox-viewer-role.yaml\n    │   └── ...\n    ├── crd/                     # Individual CRD files (examples)\n    │   ├── busyboxes.example.com.testproject.org.yaml\n    │   └── ...\n    ├── cert-manager/\n    │   ├── metrics-certs.yaml\n    │   ├── selfsigned-issuer.yaml\n    │   └── serving-cert.yaml\n    ├── manager/\n    │   └── manager.yaml\n    ├── metrics/\n    │   └── controller-manager-metrics-service.yaml\n    ├── webhook/\n    │   ├── validating-webhook-configuration.yaml\n    │   └── webhook-service.yaml\n    ├── monitoring/\n    │   └── servicemonitor.yaml\n    └── extras/                  # Custom resources (if any)\n        ├── my-service.yaml\n        └── my-config.yaml\n```\n\n<aside class=\"note\">\n<H1>Chart Structure</H1>\n\nThe chart structure mirrors your project's resources:\n\n- Standard resources (RBAC, manager, webhooks, CRDs) go into dedicated template directories\n- Other resources (Services, ConfigMaps, Secrets) go into `templates/extras/` with Helm templating\n- **Custom Resource instances** from `config/samples/` are **not included in the chart**\n\nBy default, `make build-installer` does not include samples in `dist/install.yaml`. If you manually add CR instances to your kustomize output, the Helm plugin will ignore them.\n\n</aside>\n\n<aside class=\"note\">\n<H1> Why CRDs are added under templates? </H1>\n\nAlthough [Helm best practices](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you) recommend placing CRDs under a top-level `crds/` directory, the Kubebuilder Helm plugin intentionally places them under `templates/crd`.\n\nThe rationale is tied to how Helm itself handles CRDs.\nBy default, Helm will install CRDs once during the initial release,\nbut it will **ignore CRD changes** on subsequent upgrades.\n\nThis can lead to surprising behavior where chart upgrades silently\nskip CRD updates, leaving clusters out of sync.\n\nTo avoid endorsing this behavior, the Kubebuilder plugin follows the approach of packaging\nCRDs inside `templates/`. In this mode, Helm treats CRDs like\nany other resource, ensuring they are applied and upgraded as expected.\nWhile this prevents mixing CRDs and CRs of the same type in a single chart (since Helm cannot wait between creation steps), it ensures predictable and explicit lifecycle management of CRDs.\n\nIn short:\n- **Helm `crds/` directory** → one-time install only, no upgrades.\n- **Kubebuilder `templates/crd`** → CRDs managed like other manifests, upgrades included.\n\nThis design choice prioritizes correctness and maintainability over Helm's default convention,\nwhile leaving room for future improvements (such as scaffolding separate charts for APIs and controllers).\n</aside>\n\n## Post-Install Notes\n\nThe plugin generates a `NOTES.txt` template that displays helpful information after `helm install` or `helm upgrade`:\n\n- Installation confirmation with release name and namespace\n- Commands to verify the deployment (kubectl get pods, CRDs)\n- How to get more information using helm commands\n\nThe `NOTES.txt` file is preserved on subsequent runs (unless `--force` is used), allowing you to customize the post-install message for your users.\n\n## Values Configuration\n\nThe generated `values.yaml` provides configuration options extracted from your actual deployment.\nNamespace creation is not managed by the chart; use Helm's `--namespace` and `--create-namespace` flags when installing.\n\n**Example**\n\n```yaml\n## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: controller\n    tag: latest\n    pullPolicy: IfNotPresent\n\n  ## Arguments\n  ##\n  args:\n    - --leader-elect\n\n  ## Environment variables\n  ##\n  env:\n    - name: BUSYBOX_IMAGE\n      value: busybox:1.36.1\n    - name: MEMCACHED_IMAGE\n      value: memcached:1.6.26-alpine3.19\n\n  ## Image pull secrets\n  ##\n  imagePullSecrets: []\n  # Example:\n  # imagePullSecrets:\n  #   - name: myregistrykey\n\n  ## Pod-level security settings\n  ##\n  podSecurityContext:\n    runAsNonRoot: true\n    seccompProfile:\n        type: RuntimeDefault\n\n  ## Container-level security settings\n  ##\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n        drop:\n            - ALL\n    readOnlyRootFilesystem: true\n\n  ## Resource limits and requests\n  ##\n  resources:\n    limits:\n        cpu: 500m\n        memory: 128Mi\n    requests:\n        cpu: 10m\n        memory: 64Mi\n\n  ## Manager pod's affinity\n  ##\n  affinity: {}\n  # Example:\n  # affinity:\n  #   nodeAffinity:\n  #     requiredDuringSchedulingIgnoredDuringExecution:\n  #       nodeSelectorTerms:\n  #         - matchExpressions:\n  #           - key: kubernetes.io/arch\n  #             operator: In\n  #             values:\n  #               - amd64\n  #               - arm64\n\n  ## Manager pod's node selector\n  ##\n  nodeSelector: {}\n  # Example:\n  # nodeSelector:\n  #   kubernetes.io/os: linux\n  #   disktype: ssd\n\n  ## Manager pod's tolerations\n  ##\n  tolerations: []\n  # Example:\n  # tolerations:\n  #   - key: \"node.kubernetes.io/unreachable\"\n  #     operator: \"Exists\"\n  #     effect: \"NoExecute\"\n  #     tolerationSeconds: 6000\n\n## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: 8443\n\n## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: true\n\n## Webhook server configuration\n##\nwebhook:\n  enable: true\n  # Webhook server port\n  port: 9443\n\n## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n```\n\n### Installation\n\nThe first time you run the plugin, it adds convenient Helm deployment targets to your `Makefile`:\n\n```shell\nmake helm-deploy IMG=<registry>/<project:tag>  # Deploy/upgrade the chart\nmake helm-status                                # Check release status\nmake helm-history                               # View release history\nmake helm-rollback                              # Rollback to previous version\nmake helm-uninstall                             # Remove the release\n```\n\nYou can also install manually using Helm commands:\n\n```shell\nhelm install my-release ./dist/chart \\\n  --namespace my-project-system \\\n  --create-namespace\n```\n\nThe Makefile targets use sensible defaults extracted from your project configuration (namespace from manifests, release name from project name, chart directory from `--output-dir` flag).\n\n## Flags\n\n| Flag                | Description                                                                 |\n|---------------------|-----------------------------------------------------------------------------|\n| **--manifests**     | Path to YAML file containing Kubernetes manifests (default: `dist/install.yaml`) |\n| **--output-dir** string | Output directory for chart (default: `dist`)                                |\n| **--force**         | Regenerates preserved files except `Chart.yaml` (`values.yaml`, `NOTES.txt`, `_helpers.tpl`, `.helmignore`, `test-chart.yml`) |\n\n<aside class=\"note\">\n<H1> Examples </H1>\n\nYou can find example projects in [testdata/project-v4-with-plugins](https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins).\n\n</aside>\n"
  },
  {
    "path": "docs/book/src/plugins/available/kustomize-v2.md",
    "content": "# Kustomize v2\n\n**(Default Scaffold)**\n\nThe Kustomize plugin allows you to scaffold all kustomize manifests used\nwith the language base plugin `base.go.kubebuilder.io/v4`.\nThis plugin is used to generate the manifest under the `config/` directory\nfor projects built within the `go/v4` plugin (default scaffold).\n\nProjects like [Operator-sdk][sdk] use the Kubebuilder project as a library\nand provide options for working with other languages such as Ansible and Helm.\nThe Kustomize plugin helps them maintain consistent configuration across\nlanguages. It also simplifies the creation of plugins that perform\nchanges on top of the default scaffold, removing the need for manual\nupdates across multiple language plugins. This approach allows the\ncreation of \"helper\" plugins that work with different projects\nand languages.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can check the kustomize content by looking at the `config/` directory provided in the sample `project-v4-*`\nunder the [testdata][testdata] directory of the Kubebuilder project.\n\n</aside>\n\n## How to use it\n\nIf you want your language plugin to use kustomize, use the [Bundle Plugin][bundle] to specify that your language plugin is composed of your language-specific plugin and kustomize for its configuration, as shown:\n\n```go\nimport (\n   ...\n   kustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n   golangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n   ...\n)\n\n// Bundle plugin for Golang projects scaffolded by Kubebuilder go/v4\ngov4Bundle, _ := plugin.NewBundle(plugin.WithName(golang.DefaultNameQualifier),\n    plugin.WithVersion(plugin.Version{Number: 4}),\n    plugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv4.Plugin{}), // Scaffold the config/ directory and all kustomize files\n)\n```\n\nYou can also use kustomize/v2 alone via:\n\n```sh\nkubebuilder init --plugins=kustomize/v2\n$ ls -la\ntotal 24\ndrwxr-xr-x   6 camilamacedo86  staff  192 31 Mar 09:56 .\ndrwxr-xr-x  11 camilamacedo86  staff  352 29 Mar 21:23 ..\n-rw-------   1 camilamacedo86  staff  129 26 Mar 12:01 .dockerignore\n-rw-------   1 camilamacedo86  staff  367 26 Mar 12:01 .gitignore\n-rw-------   1 camilamacedo86  staff   94 31 Mar 09:56 PROJECT\ndrwx------   6 camilamacedo86  staff  192 31 Mar 09:56 config\n```\n\nOr combined with the base language plugins:\n\n```sh\n# Provides the same scaffold of go/v4 plugin which is composition but with kustomize/v2\nkubebuilder init --plugins=kustomize/v2,base.go.kubebuilder.io/v4 --domain example.org --repo example.org/guestbook-operator\n```\n\n## Subcommands\n\nThe kustomize plugin implements the following subcommands:\n\n* init (`$ kubebuilder init [OPTIONS]`)\n* create api (`$ kubebuilder create api [OPTIONS]`)\n* create webhook (`$ kubebuilder create api [OPTIONS]`)\n\n<aside class=\"note\">\n<h1>Create API and Webhook</h1>\n\nThe implementation for the `create api` subcommand scaffolds the kustomize\nmanifests specific to each API. See more [here][kustomize-create-api].\nThe same applies to `create webhook`.\n\n</aside>\n\n## Affected files\n\nThe following scaffolds will be created or updated by this plugin:\n\n* `config/*`\n\n## Further resources\n\n* Check the kustomize [plugin implementation](https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/plugins/common/kustomize)\n* Check the [kustomize documentation][kustomize-docs]\n* Check the [kustomize repository][kustomize-github]\n\n[sdk]:https://github.com/operator-framework/operator-sdk\n[kustomize-docs]: https://kustomize.io/\n[kustomize-github]: https://github.com/kubernetes-sigs/kustomize\n[kustomize-replacements]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/replacements/\n[kustomize-vars]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/vars/\n[release-notes-v5]: https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv5.0.0\n[release-notes-v4]: https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv4.0.0\n[testdata]: ./../../../../../testdata/\n[bundle]: ./../../../../../pkg/plugin/bundle.go\n[kustomize-create-api]: ./../../../../../pkg/plugins/common/kustomize/v2/scaffolds/api.go\n"
  },
  {
    "path": "docs/book/src/plugins/available-plugins.md",
    "content": "# Available plugins\n\nThis section describes the plugins supported and shipped in with the Kubebuilder project.\n\n{{#include to-scaffold-project.md }}\n{{#include to-add-optional-features.md }}\n{{#include to-be-extended.md }}\n\n[plugin-versions]: plugins-versioning.md"
  },
  {
    "path": "docs/book/src/plugins/extending/custom-markers.md",
    "content": "# Creating Custom Markers\n\n## Overview\n\nWhen using Kubebuilder as a library, you may need to scaffold files with extensions that aren't natively supported by Kubebuilder's marker system. This guide shows you how to create custom marker support for any file extension.\n\n## When to Use Custom Markers\n\nCustom markers are useful when:\n\n- You're building an external plugin for languages not natively supported by Kubebuilder\n- You want to scaffold files with custom extensions (`.rs`, `.java`, `.py`, `.tpl`, etc.)\n- You need scaffolding markers in non-Go files for your own use cases\n- Your file extensions aren't (and shouldn't be) part of the core `commentsByExt` map\n\n## Understanding Markers\n\nMarkers are special comments used by Kubebuilder for scaffolding purposes. They indicate where code can be inserted or modified. The core Kubebuilder marker system only supports `.go`, `.yaml`, and `.yml` files by default.\n\nExample of a marker in a Go file:\n```go\n// +kubebuilder:scaffold:imports\n```\n\n## Implementation Example\n\nHere's how to implement custom markers for Rust files (`.rs`). This same pattern can be applied to any file extension.\n\n### Define Your Marker Type\n\n```go\n// pkg/markers/rust.go\npackage markers\n\nimport (\n    \"fmt\"\n    \"path/filepath\"\n    \"strings\"\n)\n\nconst RustPluginPrefix = \"+rust:scaffold:\"\n\ntype RustMarker struct {\n    prefix  string\n    comment string\n    value   string\n}\n\nfunc NewRustMarker(path string, value string) (RustMarker, error) {\n    ext := filepath.Ext(path)\n    if ext != \".rs\" {\n        return RustMarker{}, fmt.Errorf(\"expected .rs file, got %s\", ext)\n    }\n\n    return RustMarker{\n        prefix:  formatPrefix(RustPluginPrefix),\n        comment: \"//\",\n        value:   value,\n    }, nil\n}\n\nfunc (m RustMarker) String() string {\n    return m.comment + \" \" + m.prefix + m.value\n}\n\nfunc formatPrefix(prefix string) string {\n    trimmed := strings.TrimSpace(prefix)\n    var builder strings.Builder\n    if !strings.HasPrefix(trimmed, \"+\") {\n        builder.WriteString(\"+\")\n    }\n    builder.WriteString(trimmed)\n    if !strings.HasSuffix(trimmed, \":\") {\n        builder.WriteString(\":\")\n    }\n    return builder.String()\n}\n```\n\n<aside class=\"note\">\n<h1>Implementation Reference</h1>\n\nThe `formatPrefix` implementation shown above is adapted from Kubebuilder's internal\n[markerPrefix function](https://github.com/kubernetes-sigs/kubebuilder/blob/master/pkg/machinery/marker.go).\n\n</aside>\n\n### Use in Template Generation\n\n```go\npackage templates\n\nimport (\n    \"fmt\"\n    \"github.com/yourorg/yourplugin/pkg/markers\"\n)\n\nfunc GenerateRustFile(projectName string) (string, error) {\n    marker, err := markers.NewRustMarker(\"src/main.rs\", \"imports\")\n    if err != nil {\n        return \"\", err\n    }\n\n    content := fmt.Sprintf(`// Generated by Rust Plugin\n%s\n\nuse std::error::Error;\n\nfn main() -> Result<(), Box<dyn Error>> {\n    println!(\"Hello from %s!\");\n    Ok(())\n}\n`, marker.String(), projectName)\n\n    return content, nil\n}\n\nfunc GenerateCargoToml(projectName string) string {\n    return fmt.Sprintf(`[package]\nname = \"%s\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\n`, projectName)\n}\n```\n\n### Integrate with External Plugin\n\n```go\npackage main\n\nimport (\n    \"bufio\"\n    \"encoding/json\"\n    \"fmt\"\n    \"io\"\n    \"os\"\n\n    \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n    \"github.com/yourorg/yourplugin/pkg/markers\"\n)\n\nfunc main() {\n    // External plugins communicate via JSON over STDIN/STDOUT\n    reader := bufio.NewReader(os.Stdin)\n    input, err := io.ReadAll(reader)\n    if err != nil {\n        returnError(fmt.Errorf(\"error reading STDIN: %w\", err))\n        return\n    }\n\n    pluginRequest := &external.PluginRequest{}\n    err = json.Unmarshal(input, pluginRequest)\n    if err != nil {\n        returnError(fmt.Errorf(\"error unmarshaling request: %w\", err))\n        return\n    }\n\n    var response external.PluginResponse\n\n    switch pluginRequest.Command {\n    case \"init\":\n        response = handleInit(pluginRequest)\n    default:\n        response = external.PluginResponse{\n            Command: pluginRequest.Command,\n            Error:   true,\n            ErrorMsgs: []string{fmt.Sprintf(\"unknown command: %s\", pluginRequest.Command)},\n        }\n    }\n\n    output, err := json.Marshal(response)\n    if err != nil {\n        fmt.Fprintf(os.Stderr, \"failed to marshal response: %v\\n\", err)\n        os.Exit(1)\n    }\n    fmt.Printf(\"%s\", output)\n}\n\nfunc handleInit(req *external.PluginRequest) external.PluginResponse {\n    // Create Rust file with custom markers\n    marker, err := markers.NewRustMarker(\"src/main.rs\", \"imports\")\n    if err != nil {\n        return external.PluginResponse{\n            Command: \"init\",\n            Error:   true,\n            ErrorMsgs: []string{fmt.Sprintf(\"failed to create Rust marker: %v\", err)},\n        }\n    }\n\n    fileContent := fmt.Sprintf(`// Generated by Rust Plugin\n%s\n\nuse std::error::Error;\n\nfn main() -> Result<(), Box<dyn Error>> {\n    println!(\"Hello from Rust!\");\n    Ok(())\n}\n`, marker.String())\n\n    // External plugins use \"universe\" to represent file changes.\n    // \"universe\" is a map from file paths to their file contents,\n    // passed through the plugin chain to coordinate file generation.\n    universe := make(map[string]string)\n    universe[\"src/main.rs\"] = fileContent\n\n    return external.PluginResponse{\n        Command:  \"init\",\n        Universe: universe,\n    }\n}\n\nfunc returnError(err error) {\n    response := external.PluginResponse{\n        Error:     true,\n        ErrorMsgs: []string{err.Error()},\n    }\n    output, marshalErr := json.Marshal(response)\n    if marshalErr != nil {\n        fmt.Fprintf(os.Stderr, \"failed to marshal error response: %v\\n\", marshalErr)\n        os.Exit(1)\n    }\n    fmt.Printf(\"%s\", output)\n}\n```\n\n## Adapting for Other Languages\n\nTo support other file extensions, modify the marker implementation by changing:\n\n- The comment syntax (e.g., `//` for Java, `#` for Python, `{{/* ... */}}` for templates)\n- The file extension check (e.g., `.java`, `.py`, `.tpl`)\n- The marker prefix (e.g., `+java:scaffold:`, `+python:scaffold:`)\n\nFor more information on creating external plugins, see [External Plugins](external-plugins.md)."
  },
  {
    "path": "docs/book/src/plugins/extending/extending_cli_features_and_plugins.md",
    "content": "# Extending CLI Features and Plugins\n\nKubebuilder provides an extensible architecture to scaffold\nprojects using plugins. These plugins allow you to customize the CLI\nbehavior or integrate new features.\n\nIn this guide, we’ll explore how to extend CLI features,\ncreate custom plugins, and bundle multiple plugins.\n\n## Creating Custom Plugins\n\nTo create a custom plugin, you need to implement\nthe [Kubebuilder Plugin interface][plugin-interface].\n\nThis interface allows your plugin to hook into Kubebuilder’s\ncommands (`init`, `create api`, `create webhook`, etc.)\nand add custom logic.\n\n### Example of a Custom Plugin\n\nYou can create a plugin that generates both\nlanguage-specific scaffolds and the necessary configuration files,\nusing the [Bundle Plugin](#bundle-plugin). This example shows how to\ncombine the Golang plugin with a Kustomize plugin:\n\n```go\nimport (\n    kustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n    golangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n)\n\nmylanguagev1Bundle, _ := plugin.NewBundle(\n    plugin.WithName(\"mylanguage.kubebuilder.io\"),\n    plugin.WithVersion(plugin.Version{Number: 1}),\n    plugin.WithPlugins(kustomizecommonv2.Plugin{}, mylanguagev1.Plugin{}),\n)\n```\n\nThis composition allows you to scaffold a common\nconfiguration base (via Kustomize) and the\nlanguage-specific files (via `mylanguagev1`).\n\nYou can also use your plugin to scaffold specific\nresources like CRDs and controllers, using\nthe `create api` and `create webhook` subcommands.\n\n### Plugin Subcommands\n\nPlugins are responsible for implementing the code that will be executed when the sub-commands are called.\nYou can create a new plugin by implementing the [Plugin interface][plugin-interface].\n\nOn top of being a `Base`, a plugin should also implement the [`SubcommandMetadata`][plugin-subc-metadata]\ninterface so it can be run with a CLI. Optionally, a custom help\ntext for the target  command can be set; this method can be a no-op, which will\npreserve the default help text set by the [cobra][cobra] command\nconstructors.\n\nKubebuilder CLI plugins wrap scaffolding and CLI features in conveniently packaged Go types that are executed by the\n`kubebuilder` binary, or any binary which imports them. More specifically, a plugin configures the execution of one\nof the following CLI commands:\n\n- `init`: Initializes the project structure.\n- `create api`: Scaffolds a new API and controller.\n- `create webhook`: Scaffolds a new webhook.\n- `edit`: edit the project structure.\n\nHere’s an example of using the `init` subcommand with a custom plugin:\n\n```sh\nkubebuilder init --plugins=mylanguage.kubebuilder.io/v1\n```\n\nThis would initialize a project using the `mylanguage` plugin.\n\n### Plugin Keys\n\nPlugins are identified by a key of the form `<name>/<version>`.\nThere are two ways to specify a plugin to run:\n\n- Setting `kubebuilder init --plugins=<plugin key>`, which will initialize a project configured for plugin with key\n  `<plugin key>`.\n\n- A `layout: <plugin key>` in the scaffolded [PROJECT configuration file][project-file-config]. Commands (except for `init`, which scaffolds this file) will look at this value before running to choose which plugin to run.\n\nBy default, `<plugin key>` will be `go.kubebuilder.io/vX`, where `X` is some integer.\n\nFor a full implementation example, check out Kubebuilder's native [`go.kubebuilder.io`][kb-go-plugin] plugin.\n\n### Plugin naming\n\nPlugin names must be DNS1123 labels and should be fully qualified, i.e. they have a suffix like\n`.example.com`. For example, the base Go scaffold used with `kubebuilder` commands has name `go.kubebuilder.io`.\nQualified names prevent conflicts between plugin names; both `go.kubebuilder.io` and `go.example.com` can both scaffold\nGo code and can be specified by a user.\n\n### Plugin versioning\n\nA plugin's `Version()` method returns a [`plugin.Version`][plugin-version-type] object containing an integer value\nand optionally a stage string of either \"alpha\" or \"beta\". The integer denotes the current version of a plugin.\nTwo different integer values between versions of plugins indicate that the two plugins are incompatible. The stage\nstring denotes plugin stability:\n\n- `alpha`: should be used for plugins that are frequently changed and may break between uses.\n- `beta`: should be used for plugins that are only changed in minor ways, ex. bug fixes.\n\n### Boilerplates\n\nThe Kubebuilder internal plugins use boilerplates to generate the\nfiles of code. Kubebuilder uses templating to scaffold files for plugins.\nFor instance, when creating a new project, the `go/v4` plugin\nscaffolds the `go.mod` file using a template defined in\nits implementation.\n\nYou can extend this functionality in your custom\nplugin by defining your own templates and using\n[Kubebuilder’s machinery library][machinery] to generate files.\nThis library allows you to:\n\n- Define file I/O behaviors.\n- Add [markers][markers-scaffold] to the scaffolded files.\n- Specify templates for your scaffolds.\n\n#### Example: Boilerplate\n\nFor instance, the go/v4 scaffolds the `go.mod` file by defining an object that [implements the machinery interface][machinery].\nThe raw template is set to the `TemplateBody` field on the `Template.SetTemplateDefaults` method:\n\n```go\n{{#include ./../../../../../pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go}}\n```\n\nSuch object that implements the machinery interface will later pass to the\nexecution of scaffold:\n\n```go\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *initScaffolder) Scaffold() error {\n\tlog.Println(\"Writing scaffold for you to edit...\")\n\n\t// Initialize the machinery.Scaffold that will write the boilerplate file to disk\n\t// The boilerplate file needs to be scaffolded as a separate step as it is going to\n\t// be used by the rest of the files, even those scaffolded in this command call.\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\t...\n\n\treturn scaffold.Execute(\n\t\t...\n\t\t&templates.GoMod{\n\t\t\tControllerRuntimeVersion: ControllerRuntimeVersion,\n\t\t},\n\t\t...\n\t)\n}\n```\n\n#### Example: Overwriting a File in a Plugin\n\nLet's imagine that when a subcommand is called, you want\nto overwrite an existing file.\n\nFor example, to modify the `Makefile` and add custom build steps,\nin the definition of your Template you can use the following option:\n\n```go\nf.IfExistsAction = machinery.OverwriteFile\n```\n\nBy using those options, your plugin can take control\nof certain files generated by Kubebuilder’s default scaffolds.\n\n## Customizing Existing Scaffolds\n\nKubebuilder provides utility functions to help you modify the default scaffolds. By using the [plugin utilities][plugin-utils], you can insert, replace, or append content to files generated by Kubebuilder, giving you full control over the scaffolding process.\n\nThese utilities allow you to:\n\n- **Insert content**: Add content at a specific location within a file.\n- **Replace content**: Search for and replace specific sections of a file.\n- **Append content**: Add content to the end of a file without removing or altering the existing content.\n\n### Example\n\nIf you need to insert custom content into a scaffolded file,\nyou can use the `InsertCode` function provided by the plugin utilities:\n\n```go\npluginutil.InsertCode(filename, target, code)\n```\n\nThis approach enables you to extend and modify the generated\nscaffolds while building custom plugins.\n\nFor more details, refer to the [Kubebuilder plugin utilities][kb-utils].\n\n## Bundle Plugin\n\nPlugins can be bundled to compose more complex scaffolds.\nA plugin bundle is a composition of multiple plugins that\nare executed in a predefined order. For example:\n\n```go\nmyPluginBundle, _ := plugin.NewBundle(\n    plugin.WithName(\"myplugin.example.com\"),\n    plugin.WithVersion(plugin.Version{Number: 1}),\n    plugin.WithPlugins(pluginA.Plugin{}, pluginB.Plugin{}, pluginC.Plugin{}),\n)\n```\n\nThis bundle will execute the `init` subcommand for each\nplugin in the specified order:\n\n1. `pluginA`\n2. `pluginB`\n3. `pluginC`\n\nThe following command will run the bundled plugins:\n\n```sh\nkubebuilder init --plugins=myplugin.example.com/v1\n```\n\n## CLI system\n\nPlugins are run using a [`CLI`][cli] object, which maps a plugin type to a subcommand and calls that plugin's methods.\nFor example, writing a program that injects an `Init` plugin into a `CLI` then calling `CLI.Run()` will call the\nplugin's [SubcommandMetadata][plugin-sub-command], [UpdatesMetadata][plugin-update-meta] and `Run` methods with information a user has passed to the\nprogram in `kubebuilder init`. Following an example:\n\n```go\npackage cli\n\nimport (\n\tlog \"log/slog\"\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/cli\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\tkustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\tdeployimagev1alpha1 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\"\n    golangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n\n)\n\nvar (\n\t// The following is an example of the commands\n\t// that you might have in your own binary\n\tcommands = []*cobra.Command{\n\t\tmyExampleCommand.NewCmd(),\n\t}\n\talphaCommands = []*cobra.Command{\n\t\tmyExampleAlphaCommand.NewCmd(),\n\t}\n)\n\n// GetPluginsCLI returns the plugins based CLI configured to be used in your CLI binary\nfunc GetPluginsCLI() (*cli.CLI) {\n\t// Bundle plugin which built the golang projects scaffold by Kubebuilder go/v4\n\tgov3Bundle, _ := plugin.NewBundleWithOptions(plugin.WithName(golang.DefaultNameQualifier),\n\t\tplugin.WithVersion(plugin.Version{Number: 3}),\n\t\tplugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv4.Plugin{}),\n\t)\n\n\n\tc, err := cli.New(\n\t\t// Add the name of your CLI binary\n\t\tcli.WithCommandName(\"example-cli\"),\n\n\t\t// Add the version of your CLI binary\n\t\tcli.WithVersion(versionString()),\n\n\t\t// Register the plugins options which can be used to do the scaffolds via your CLI tool. See that we are using as example here the plugins which are implemented and provided by Kubebuilder\n\t\tcli.WithPlugins(\n\t\t\tgov3Bundle,\n\t\t\t&deployimagev1alpha1.Plugin{},\n\t\t),\n\n\t\t// Defines what will be the default plugin used by your binary. It means that will be the plugin used if no info be provided such as when the user runs `kubebuilder init`\n\t\tcli.WithDefaultPlugins(cfgv3.Version, gov3Bundle),\n\n\t\t// Define the default project configuration version which will be used by the CLI when none is informed by --project-version flag.\n\t\tcli.WithDefaultProjectVersion(cfgv3.Version),\n\n\t\t// Adds your own commands to the CLI\n\t\tcli.WithExtraCommands(commands...),\n\n\t\t// Add your own alpha commands to the CLI\n\t\tcli.WithExtraAlphaCommands(alphaCommands...),\n\n\t\t// Adds the completion option for your CLI\n\t\tcli.WithCompletion(),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn c\n}\n\n// versionString returns the CLI version\nfunc versionString() string {\n\t// return your binary project version\n}\n```\n\nThis program can then be built and run in the following ways:\n\nDefault behavior:\n\n```sh\n# Initialize a project with the default Init plugin, \"go.example.com/v1\".\n# This key is automatically written to a PROJECT config file.\n$ my-bin-builder init\n# Create an API and webhook with \"go.example.com/v1\" CreateAPI and\n# CreateWebhook plugin methods. This key was read from the config file.\n$ my-bin-builder create api [flags]\n$ my-bin-builder create webhook [flags]\n```\n\nSelecting a plugin using `--plugins`:\n\n```sh\n# Initialize a project with the \"ansible.example.com/v1\" Init plugin.\n# Like above, this key is written to a config file.\n$ my-bin-builder init --plugins ansible\n# Create an API and webhook with \"ansible.example.com/v1\" CreateAPI\n# and CreateWebhook plugin methods. This key was read from the config file.\n$ my-bin-builder create api [flags]\n$ my-bin-builder create webhook [flags]\n```\n\n### Inputs should be tracked in the PROJECT file\n\nThe CLI is responsible for managing the [PROJECT file configuration][project-file-config],\nwhich represents the configuration of the projects scaffolded by the\nCLI tool.\n\nWhen extending Kubebuilder, it is recommended to ensure that your tool\nor [External Plugin][external-plugin] properly uses the\n[PROJECT file][project-file-config] to track relevant information.\nThis ensures that other external tools and plugins can properly\nintegrate with the project. It also allows tools features to help users\nre-scaffold their projects such as using the [Alpha Commands](./../../reference/alpha_commands.md)\nto upgrade the project scaffold to a newer version of Kubebuilder, ensuring the tracked information in the\nPROJECT file can be leveraged for various purposes.\n\nFor example, plugins can check whether they support the project setup\nand re-execute commands based on the tracked inputs.\n\n#### Example\n\nBy running the following command to use the\n[Deploy Image][deploy-image] plugin to scaffold\nan API and its controller:\n\n```sh\nkubebyilder create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:memcached:1.6.26-alpine3.19 --image-container-command=\"memcached,--memory-limit=64,-o,modern,-v\" --image-container-port=\"11211\" --run-as-user=\"1001\" --plugins=\"deploy-image/v1-alpha\" --make=false\n```\n\nThe following entry would be added to the PROJECT file:\n\n```yaml\n...\nplugins:\n  deploy-image.go.kubebuilder.io/v1-alpha:\n    resources:\n    - domain: testproject.org\n      group: example.com\n      kind: Memcached\n      options:\n        containerCommand: memcached,--memory-limit=64,-o,modern,-v\n        containerPort: \"11211\"\n        image: memcached:memcached:1.6.26-alpine3.19\n        runAsUser: \"1001\"\n      version: v1alpha1\n    - domain: testproject.org\n      group: example.com\n      kind: Busybox\n      options:\n        image: busybox:1.36.1\n      version: v1alpha1\n...\n```\n\nBy inspecting the PROJECT file, it becomes possible to understand how\nthe plugin was used and what inputs were provided. This not only allows\nre-execution of the command based on the tracked data but also enables\ncreating features or plugins that can rely on this information.\n\n[sdk]: https://github.com/operator-framework/operator-sdk\n[plugin-interface]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin\n[machinery]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/machinery\n[plugin-subc-metadata]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#SubcommandMetadata\n[plugin-version-type]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#Version\n[bundle-plugin-doc]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#Bundle\n[deprecate-plugin-doc]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#Deprecated\n[plugin-sub-command]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#Subcommand\n[plugin-update-meta]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin#UpdatesMetadata\n[plugin-utils]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\n[markers-scaffold]: ./../../reference/markers/scaffold.md\n[kb-utils]: https://github.com/kubernetes-sigs/kubebuilder/blob/book-v4/pkg/plugin/util/util.go\n[project-file-config]: ./../../reference/project-config.md\n[cli]: https://github.com/kubernetes-sigs/kubebuilder/tree/book-v4/pkg/cli\n[kb-go-plugin]: https://github.com/kubernetes-sigs/kubebuilder/tree/book-v4/pkg/plugins/golang/v4\n[cobra]: https://github.com/spf13/cobra\n[external-plugin]: external-plugins.md\n[deploy-image]: ./../available/deploy-image-plugin-v1-alpha.md\n[upgrade-assistant]: ./../../reference/rescaffold.md\n"
  },
  {
    "path": "docs/book/src/plugins/extending/external-plugins.md",
    "content": "# Creating External Plugins for Kubebuilder\n\n## Overview\n\nKubebuilder's functionality can be extended through external plugins.\nThese plugins are executables (written in any language) that follow an\nexecution pattern recognized by Kubebuilder. Kubebuilder interacts with\nthese plugins via `stdin` and `stdout`, enabling seamless communication.\n\n## Why Use External Plugins?\n\nExternal plugins enable third-party solution maintainers to integrate their tools with Kubebuilder.\nMuch like Kubebuilder's own plugins, these can be opt-in, offering users\nflexibility in tool selection. By developing plugins in their repositories,\nmaintainers ensure updates are aligned with their CI pipelines and can\nmanage any changes within their domain of responsibility.\n\nIf you are interested in this type of integration, collaborating with the\nmaintainers of the third-party solution is recommended. Kubebuilder's maintainers\nare always willing to provide support in extending its capabilities.\n\n## How to Write an External Plugin\n\nCommunication between Kubebuilder and an external plugin occurs via\nstandard I/O. Any language can be used to create the plugin, as long\nas it follows the [PluginRequest][code-plugin-external] and [PluginResponse][code-plugin-external]\nstructures.\n\n`PluginRequest` contains the data collected from the CLI and any previously executed plugins. Kubebuilder sends this data as a JSON object to the external plugin via `stdin`.\n\n**Fields:**\n- `apiVersion`: Version of the PluginRequest schema.\n- `args`: Command-line arguments passed to the plugin.\n- `command`: The subcommand being executed (e.g., `init`, `create api`, `create webhook`, `edit`).\n- `universe`: Map of file paths to contents, updated across the plugin chain.\n- `pluginChain` (optional): Array of plugin keys in the order they were executed. External plugins can inspect this to tailor behavior based on other plugins that ran (for example, `go.kubebuilder.io/v4` or `kustomize.common.kubebuilder.io/v2`).\n- `config` (optional): Serialized PROJECT file configuration for the current project. Use it to inspect metadata, existing resources, or plugin-specific settings. Kubebuilder omits this field before the PROJECT file exists—typically during the first `init`—so plugins should check for its presence.\n\n\n**Note:** Whenever Kubebuilder has a PROJECT file available (for example during `create api`, `create webhook`, `edit`, or a subsequent `init` run), `PluginRequest` includes the `config` field. During the very first `init` run the field is omitted because the PROJECT file does not exist yet.\n\n**Example `PluginRequest` (triggered by `kubebuilder init --plugins go/v4,sampleexternalplugin/v1 --domain my.domain`):**\n\n```json\n{\n  \"apiVersion\": \"v1alpha1\",\n  \"args\": [\"--domain\", \"my.domain\"],\n  \"command\": \"init\",\n  \"universe\": {},\n  \"pluginChain\": [\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\", \"sampleexternalplugin/v1\"]\n}\n```\n\n**Example `PluginRequest` for `create api` (includes `config`):**\n```json\n{\n  \"apiVersion\": \"v1alpha1\",\n  \"args\": [\"--group\", \"crew\", \"--version\", \"v1\", \"--kind\", \"Captain\"],\n  \"command\": \"create api\",\n  \"universe\": {},\n  \"pluginChain\": [\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\", \"sampleexternalplugin/v1\"],\n  \"config\": {\n    \"domain\": \"my.domain\",\n    \"repo\": \"github.com/example/my-project\",\n    \"projectName\": \"my-project\",\n    \"version\": \"3\",\n    \"layout\": [\"go.kubebuilder.io/v4\"],\n    \"multigroup\": false,\n    \"resources\": []\n  }\n}\n```\n\n**Example `PluginRequest` (triggered by `kubebuilder edit --plugins sampleexternalplugin/v1`):**\n\n```json\n{\n  \"apiVersion\": \"v1alpha1\",\n  \"args\": [],\n  \"command\": \"edit\",\n  \"universe\": {}\n}\n```\n\n### PluginResponse\n\n`PluginResponse` contains the modifications made by the plugin to the project. This data is serialized as JSON and returned to Kubebuilder through `stdout`.\n\n**Example `PluginResponse`:**\n```json\n{\n  \"apiVersion\": \"v1alpha1\",\n  \"command\": \"edit\",\n  \"metadata\": {\n    \"description\": \"The `edit` subcommand adds Prometheus instance configuration for monitoring your operator.\",\n    \"examples\": \"kubebuilder edit --plugins sampleexternalplugin/v1\"\n  },\n  \"universe\": {\n    \"config/prometheus/prometheus.yaml\": \"# Prometheus CR manifest...\",\n    \"config/prometheus/kustomization.yaml\": \"resources:\\n  - prometheus.yaml\\n\",\n    \"config/default/kustomization_prometheus_patch.yaml\": \"# Instructions for enabling Prometheus...\"\n  },\n  \"error\": false,\n  \"errorMsgs\": []\n}\n```\n\n<aside>\n<H1> </H1>\n\nAvoid printing directly to `stdout` in your external plugin.\nSince communication between Kubebuilder and the plugin occurs through\n`stdin` and `stdout` using structured JSON, any unexpected output\n(like debug logs) may cause errors. Write logs to a file if needed.\n\n</aside>\n\n## How to Use an External Plugin\n\n### Prerequisites\n\n- Kubebuilder CLI version > 3.11.0\n- An executable for the external plugin\n- Plugin path configuration using `${EXTERNAL_PLUGINS_PATH}` or default OS-based paths:\n  - Linux: `$HOME/.config/kubebuilder/plugins/${name}/${version}/${name}`\n  - macOS: `~/Library/Application Support/kubebuilder/plugins/${name}/${version}/${name}`\n\n**Example:** For a plugin `foo.acme.io` version `v2` on Linux, the path would be `$HOME/.config/kubebuilder/plugins/foo.acme.io/v2/foo.acme.io`.\n\n### Available Subcommands\n\nExternal plugins can support the following Kubebuilder subcommands:\n- `init`: Project initialization\n- `create api`: Scaffold Kubernetes API definitions\n- `create webhook`: Scaffold Kubernetes webhooks\n- `edit`: Update project configuration\n\n**Optional subcommands for enhanced user experience:**\n- `metadata`: Provide plugin descriptions and examples with the `--help` flag.\n- `flags`: Inform Kubebuilder of supported flags, enabling early error detection.\n\n<aside class=\"note\">\n<h1>More about `flags` subcommand</h1>\n\nThe `flags` subcommand in an external plugin allows for early error detection by informing Kubebuilder about the flags the plugin supports. If an unsupported flag is identified, Kubebuilder can issue an error before the plugin is called to execute.\nIf a plugin does not implement the `flags` subcommand, Kubebuilder will pass all flags to the plugin, making it the external plugin's responsibility to handle any invalid flags.\n\n</aside>\n\n### Configuring Plugin Path\n\nSet the environment variable `$EXTERNAL_PLUGINS_PATH`\nto specify a custom plugin binary path:\n\n```sh\nexport EXTERNAL_PLUGINS_PATH=<custom-path>\n```\n\nOtherwise, Kubebuilder would search for the plugins in a default path based on your OS.\n\n### Example CLI Commands\n\nYou can now use it by calling the CLI commands:\n\n```sh\n# Add Prometheus monitoring to an existing project\nkubebuilder edit --plugins sampleexternalplugin/v1\n\n# Update an existing project with Prometheus monitoring\nkubebuilder edit --plugins sampleexternalplugin/v1\n\n# Display help information for the init subcommand\nkubebuilder init --plugins sampleexternalplugin/v1 --help\n\n# Display help information for the edit subcommand\nkubebuilder edit --plugins sampleexternalplugin/v1 --help\n\n# Plugin chaining example: Use go/v4 plugin first, then apply external plugin\nkubebuilder edit --plugins go/v4,sampleexternalplugin/v1\n```\n\n## Further resources\n\n- A [sample external plugin written in Go](https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/simple-external-plugin-tutorial/testdata/sampleexternalplugin/v1)\n- A [sample external plugin written in Python](https://github.com/rashmigottipati/POC-Phase2-Plugins)\n- A [sample external plugin written in JavaScript](https://github.com/Eileen-Yu/kb-js-plugin)\n\n[code-plugin-external]: https://github.com/kubernetes-sigs/kubebuilder/blob/book-v4/pkg/plugin/external/types.go\n"
  },
  {
    "path": "docs/book/src/plugins/extending/testing-plugins.md",
    "content": "# Write E2E Tests\n\nYou can check the [Kubebuilder/v4/test/e2e/utils][utils-kb] package, which offers `TestContext` with rich methods:\n\n- [NewTestContext][new-context] helps define:\n    - A temporary folder for testing projects.\n    - A temporary controller-manager image.\n    - The [Kubectl execution method][kubectl-ktc].\n    - The CLI executable (whether `kubebuilder`, `operator-sdk`, or your extended CLI).\n\nOnce defined, you can use `TestContext` to:\n\n1. **Setup the testing environment**, e.g.:\n    - Clean up the environment and create a temporary directory. See [Prepare][prepare-method].\n    - Install prerequisite CRDs. See [InstallCertManager][cert-manager-install], [InstallPrometheusManager][prometheus-manager-install].\n\n2. **Validate the plugin behavior**, e.g.:\n    - Trigger the plugin's bound subcommands. See [Init][init-subcommand], [CreateAPI][create-api-subcommand].\n    - Use [PluginUtil][plugin-util] to verify scaffolded outputs. See [InsertCode][insert-code], [ReplaceInFile][replace-in-file], [UncommentCode][uncomment-code].\n\n3. **Ensure the scaffolded output works**, e.g.:\n    - Execute commands in your `Makefile`. See [Make][make-command].\n    - Temporarily load an image of the testing controller. See [LoadImageToKindCluster][load-image-to-kind].\n    - Call Kubectl to validate running resources. See [Kubectl][kubectl-ktc].\n\n4. **Cleanup temporary resources after testing**:\n    - Uninstall prerequisite CRDs. See [UninstallPrometheusOperManager][uninstall-prometheus-manager].\n    - Delete the temporary directory. See [Destroy][destroy-method].\n\n**References**:\n- [operator-sdk e2e tests][sdk-e2e-tests]\n- [kubebuilder e2e tests][kb-e2e-tests]\n\n\n## Generate Test Samples\n\nIt's straightforward to view the content of sample projects generated\nby your plugin.\n\nFor example, Kubebuilder generates [sample projects][kb-samples] based\non different plugins to validate the layouts.\n\nYou can also use `TestContext` to generate folders of scaffolded\nprojects from your plugin. The commands are similar to those\nmentioned in [Extending CLI Features and Plugins][extending-cli].\n\nHere’s a general workflow to create a sample project using the `go/v4` plugin (`kbc` is an instance of `TestContext`):\n\n- **To initialize a project**:\n  ```go\n  By(\"initializing a project\")\n  err = kbc.Init(\n  \t\"--plugins\", \"go/v4\",\n  \t\"--project-version\", \"3\",\n  \t\"--domain\", kbc.Domain,\n  \t\"--fetch-deps=false\",\n  )\n  Expect(err).NotTo(HaveOccurred(), \"Failed to initialize a project\")\n  ```\n\n- **To define API:**\n  ```go\n  By(\"creating API definition\")\n  err = kbc.CreateAPI(\n  \t\"--group\", kbc.Group,\n  \t\"--version\", kbc.Version,\n  \t\"--kind\", kbc.Kind,\n  \t\"--namespaced\",\n  \t\"--resource\",\n  \t\"--controller\",\n  \t\"--make=false\",\n  )\n  Expect(err).NotTo(HaveOccurred(), \"Failed to create an API\")\n  ```\n\n- **To scaffold webhook configurations:**\n  ```go\n  By(\"scaffolding mutating and validating webhooks\")\n  err = kbc.CreateWebhook(\n  \t\"--group\", kbc.Group,\n  \t\"--version\", kbc.Version,\n  \t\"--kind\", kbc.Kind,\n  \t\"--defaulting\",\n  \t\"--programmatic-validation\",\n  )\n  Expect(err).NotTo(HaveOccurred(), \"Failed to create an webhook\")\n  ```\n\n[cert-manager-install]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.InstallCertManager\n[create-api-subcommand]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.CreateAPI\n[destroy-method]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.Destroy\n[extending-cli]: ./extending_cli_features_and_plugins.md\n[init-subcommand]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.Init\n[insert-code]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util#InsertCode\n[kb-e2e-tests]: https://github.com/kubernetes-sigs/kubebuilder/tree/book-v4/test/e2e\n[kb-samples]: https://github.com/kubernetes-sigs/kubebuilder/tree/book-v4/testdata\n[kubectl-ktc]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#Kubectl\n[load-image-to-kind]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.LoadImageToKindCluster\n[make-command]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.Make\n[new-context]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#NewTestContext\n[plugin-util]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\n[prepare-method]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.Prepare\n[prometheus-manager-install]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.InstallPrometheusOperManager\n[replace-in-file]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util#ReplaceInFile\n[sdk-e2e-tests]: https://github.com/operator-framework/operator-sdk/tree/master/test/e2e/go\n[uncomment-code]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/plugin/util#UncommentCode\n[uninstall-prometheus-manager]: https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/test/e2e/utils#TestContext.UninstallPrometheusOperManager\n[utils-kb]: https://github.com/kubernetes-sigs/kubebuilder/tree/book-v4/test/e2e/utils\n"
  },
  {
    "path": "docs/book/src/plugins/extending.md",
    "content": "# Extending Kubebuilder\n\nKubebuilder provides an extensible architecture to scaffold\nprojects using plugins. These plugins allow you to customize the CLI\nbehavior or integrate new features.\n\n## Overview\n\nKubebuilder’s CLI can be extended through custom plugins, allowing you to:\n\n- Build new scaffolds.\n- Enhance existing ones.\n- Add new commands and functionality to Kubebuilder’s scaffolding.\n\nThis flexibility enables you to create custom project\nsetups tailored to specific needs.\n\n<aside class=\"note\">\n<h1>Why use the Kubebuilder style?</h1>\n\nKubebuilder and SDK are both broadly adopted projects which leverage the [controller-runtime][controller-runtime] project. They both allow users to build solutions using the [Operator Pattern][operator-pattern] and follow common standards.\n\nAdopting these standards can bring significant benefits, such as joining forces on maintaining the common standards as the features provided by Kubebuilder and take advantage of the contributions made by the community. This allows you to focus on the specific needs and requirements for your plugin and use-case.\n\nAnd then, you will also be able to use custom plugins and options currently or in the future which might to be provided by these projects as any other which decides to persuade the same standards.\n\n</aside>\n\n## Options to Extend\n\nExtending Kubebuilder can be achieved in two main ways:\n\n1. **Extending CLI features and Plugins**:\n   You can import and build upon existing Kubebuilder plugins to [extend\n   its features and plugins][extending-cli]. This is useful when you need to add specific\n   features to a tool that already benefits from Kubebuilder's scaffolding system.\n   For example, [Operator SDK][sdk] leverages the [kustomize plugin][kustomize-plugin]\n   to provide language support for tools like Ansible or Helm. So that the project\n   can be focused to keep maintained only what is specific language based.\n\n2. **Creating External Plugins**:\n   You can build standalone, independent plugins as binaries. These plugins can be written in any\n   language and should follow an execution pattern that Kubebuilder recognizes. For more information,\n   see [Creating external plugins][external-plugins].\n\nFor further details on how to extend Kubebuilder, explore the following sections:\n\n- [CLI and Plugins](./extending/extending_cli_features_and_plugins.md) to learn how to extend CLI features and plugins.\n- [External Plugins](./extending/external-plugins.md) for creating standalone plugins.\n- [E2E Tests](./extending/testing-plugins.md) to ensure your plugin functions as expected.\n\n[extending-cli]: ./extending/extending_cli_features_and_plugins.md\n[external-plugins]: ./extending/external-plugins.md\n[sdk]: https://github.com/operator-framework/operator-sdk\n[kustomize-plugin]: ./available/kustomize-v2.md\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[operator-pattern]: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"
  },
  {
    "path": "docs/book/src/plugins/kustomize-v2.md",
    "content": "# [Default Scaffold] Kustomize v2\n\nThe kustomize plugin allows you to scaffold all kustomize manifests used to work with the language base plugin `base.go.kubebuilder.io/v4`.\nThis plugin is used to generate the manifest under `config/` directory for the projects build within the go/v4 plugin (default scaffold).\n\nNote that projects such as [Operator-sdk][sdk] consume the Kubebuilder project as a lib and provide options to work with other languages\nlike Ansible and Helm. The kustomize plugin allows them to easily keep a maintained configuration and ensure that all languages have\nthe same configuration. It is also helpful if you are looking to provide nice plugins which will perform changes on top of\nwhat is scaffolded by default. With this approach we do not need to keep manually updating this configuration in all possible language plugins\nwhich uses the same and we are also\nable to create \"helper\" plugins which can work with many projects and languages.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can check the kustomize content by looking at the `config/` directory provide on the sample `project-v4-*` under the [testdata][testdata]\ndirectory of the Kubebuilder project.\n\n</aside>\n\n## When to use it\n\n- If you are looking to scaffold the kustomize configuration manifests for your own language plugin\n- If you are looking for support on Apple Silicon (`darwin/arm64`). (_Before kustomize `4.x` the binary for this plataform is not provided_)\n- If you are looking for to begin to try out the new syntax and features provide by kustomize v4 [(More info)][release-notes-v4] and v5 [(More info)][release-notes-v5]\n- If you are NOT looking to build projects which will be used on Kubernetes cluster versions < `1.22` (_The new features provides by kustomize v4 are not officially supported and might not work with kubectl < `1.22`_)\n- If you are NOT looking to rely on special URLs in resource fields\n- If you want to use [replacements][kustomize-replacements] since [vars][kustomize-vars] are deprecated and might be removed soon\n\n## How to use it\n\nIf you are looking to define that your language plugin should use kustomize use the [Bundle Plugin][bundle]\nto specify that your language plugin is a composition with your plugin responsible for scaffold\nall that is language specific and kustomize for its configuration, see:\n\n```go\nimport (\n...\n   kustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n   golangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n...\n)\n\n\t// Bundle plugin which built the golang projects scaffold by Kubebuilder go/v4\n\t// The follow code is creating a new plugin with its name and version via composition\n\t// You can define that one plugin is composite by 1 or Many others plugins\n\tgov3Bundle, _ := plugin.NewBundle(plugin.WithName(golang.DefaultNameQualifier),\n\t\tplugin.WithVersion(plugin.Version{Number: 3}),\n        plugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv4.Plugin{}), // scaffold the config/ directory and all kustomize files\n\t\t// Scaffold the Golang files and all that specific for the language e.g. go.mod, apis, controllers\n\t)\n```\n\nAlso, with Kubebuilder, you can use kustomize/v2 alone via:\n\n```sh\nkubebuilder init --plugins=kustomize/v2\n$ ls -la\ntotal 24\ndrwxr-xr-x   6 camilamacedo86  staff  192 31 Mar 09:56 .\ndrwxr-xr-x  11 camilamacedo86  staff  352 29 Mar 21:23 ..\n-rw-------   1 camilamacedo86  staff  129 26 Mar 12:01 .dockerignore\n-rw-------   1 camilamacedo86  staff  367 26 Mar 12:01 .gitignore\n-rw-------   1 camilamacedo86  staff   94 31 Mar 09:56 PROJECT\ndrwx------   6 camilamacedo86  staff  192 31 Mar 09:56 config\n```\n\nOr combined with the base language plugins:\n\n```sh\n# Provides the same scaffold of go/v4 plugin which is composition but with kustomize/v2\nkubebuilder init --plugins=kustomize/v2,base.go.kubebuilder.io/v4 --domain example.org --repo example.org/guestbook-operator\n```\n\n## Subcommands\n\nThe kustomize plugin implements the following subcommands:\n\n* init (`$ kubebuilder init [OPTIONS]`)\n* create api (`$ kubebuilder create api [OPTIONS]`)\n* create webhook (`$ kubebuilder create api [OPTIONS]`)\n\n<aside class=\"note\">\n<h1>Create API and Webhook</h1>\n\nIts implementation for the subcommand create api will scaffold the kustomize manifests\nwhich are specific for each API, see [here][kustomize-create-api]. The same applies\nto its implementation for create webhook.\n\n</aside>\n\n## Affected files\n\nThe following scaffolds will be created or updated by this plugin:\n\n* `config/*`\n\n## Further resources\n\n* Check the kustomize [plugin implementation](https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/plugins/common/kustomize)\n* Check the [kustomize documentation][kustomize-docs]\n* Check the [kustomize repository][kustomize-github]\n* Check the [release notes][release-notes-v5] for Kustomize v5.0.0\n* Check the [release notes][release-notes-v4] for Kustomuze v4.0.0\n* Also, you can compare the `config/` directory between the samples `project-v3` and `project-v4` to check the difference in the syntax of the manifests provided by default\n\n[sdk]:https://github.com/operator-framework/operator-sdk\n[testdata]: https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/\n[bundle]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/pkg/plugin/bundle.go\n[kustomize-create-api]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/pkg/plugins/common/kustomize/v2/scaffolds/api.go#L72-L84\n[kustomize-docs]: https://kustomize.io/\n[kustomize-github]: https://github.com/kubernetes-sigs/kustomize\n[kustomize-replacements]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/replacements/\n[kustomize-vars]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/vars/\n[release-notes-v5]: https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv5.0.0\n[release-notes-v4]: https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv4.0.0\n"
  },
  {
    "path": "docs/book/src/plugins/plugins-versioning.md",
    "content": "# Plugins Versioning\n\n| Name | Example                                 | Description |\n|----------|-----------------------------------------|--------|\n| Kubebuilder version | `v2.2.0`, `v2.3.0`, `v2.3.1`,  `v4.2.0` | Tagged versions of the Kubebuilder project, representing changes to the source code in this repository. See the [releases][kb-releases] page for binary releases. |\n| Project version | `\"1\"`, `\"2\"`, `\"3\"`                     | Project version defines the scheme of a `PROJECT` configuration file. This version is defined in a `PROJECT` file's `version`. |\n| Plugin version | `v2`, `v3`, `v4`                        | Represents the version of an individual plugin, as well as the corresponding scaffolding that it generates. This version is defined in a plugin key, ex. `go.kubebuilder.io/v2`. See the [design doc][cli-plugins-versioning] for more details. |\n\n### Incrementing versions\n\nFor more information on how Kubebuilder release versions work, see the [semver][semver] documentation.\n\nProject versions should only be increased if a breaking change is introduced in the PROJECT file scheme itself. Changes to the Go scaffolding or the Kubebuilder CLI *do not* affect project version.\n\nSimilarly, the introduction of a new plugin version might only lead to a new minor version release of Kubebuilder, since no breaking change is being made to the CLI itself. It'd only be a breaking change to Kubebuilder if we remove support for an older plugin version. See the plugins design doc [versioning section][cli-plugins-versioning]\nfor more details on plugin versioning.\n\n## Introducing changes to plugins\n\nChanges made to plugins only require a plugin version increase if and only if a change is made to a plugin\nthat breaks projects scaffolded with the previous plugin version. Once a plugin version `vX` is stabilized (it doesn't\nhave an \"alpha\" or \"beta\" suffix), a new plugin package should be created containing a new plugin with version\n`v(X+1)-alpha`. Typically this is done by (semantically) `cp -r pkg/plugins/golang/vX pkg/plugins/golang/v(X+1)` then updating\nversion numbers and paths. All further breaking changes to the plugin should be made in this package; the `vX`\nplugin would then be frozen to breaking changes.\n\nYou must also add a migration guide to the [migrations][migrations]\nsection of the Kubebuilder book in your PR. It should detail the steps required\nfor users to upgrade their projects from `vX` to `v(X+1)-alpha`.\n\n<aside class=\"note\">\n\n<h1>Example</h1>\n\nKubebuilder scaffolds projects with plugin `go.kubebuilder.io/v4` by default.\n\nYou create a feature that adds a new marker to the file `main.go` scaffolded by `init` that `create api` will use to update that file.\nThe changes introduced in your feature would cause errors if used with projects built with\nplugins `go.kubebuilder.io/v4` without users manually updating their projects.\n\nThus, your changes introduce a breaking change to plugin `go.kubebuilder.io`,\nand can only be merged into plugin version `v5-alpha`.\nThis plugin's package should exist already.\n\n</aside>\n\n\n[semver]: https://semver.org/\n[migrations]: ../migrations.md\n[kb-releases]:https://github.com/kubernetes-sigs/kubebuilder/releases\n[design-doc]: ./extending\n[cli-plugins-versioning]:./extending#plugin-versioning"
  },
  {
    "path": "docs/book/src/plugins/plugins.md",
    "content": "# Plugins\n\nKubebuilder's architecture is fundamentally plugin-based.\nThis design enables the Kubebuilder CLI to evolve while maintaining\nbackward compatibility with older versions, allowing users to opt-in or\nopt-out of specific features, and enabling seamless integration\nwith external tools.\n\nBy leveraging plugins, projects can extend Kubebuilder and use it as a\nlibrary to support new functionalities or implement custom scaffolding\ntailored to their users' needs. This flexibility allows maintainers\nto build on top of Kubebuilder’s foundation, adapting it to specific\nuse cases while benefiting from its powerful scaffolding engine.\n\nPlugins offer several key advantages:\n\n- **Backward compatibility**: Ensures older layouts and project structures remain functional with newer versions.\n- **Customization**: Allows users to opt-in or opt-out for specific features (i.e. [Grafana][grafana-plugin] and [Deploy Image][deploy-image] plugins)\n- **Extensibility**: Facilitates integration with third-party tools and projects that wish to provide their own [External Plugins][external-plugins], which can be used alongside Kubebuilder to modify and enhance project scaffolding or introduce new features.\n\n**For example, to initialize a project with multiple global plugins:**\n\n```sh\nkubebuilder init --plugins=pluginA,pluginB,pluginC\n```\n\n**For example, to apply custom scaffolding using specific plugins:**\n\n```sh\nkubebuilder create api --plugins=pluginA,pluginB,pluginC\nOR\nkubebuilder create webhook --plugins=pluginA,pluginB,pluginC\nOR\nkubebuilder edit --plugins=pluginA,pluginB,pluginC\n```\n\nThis section details the available plugins, how to extend Kubebuilder,\nand how to create your own plugins while following the same layout structures.\n\n- [Available Plugins](./available-plugins.md)\n- [Extending](./extending.md)\n- [Plugins Versioning](./plugins-versioning.md)\n\n[extending-cli]: extending.md\n[grafana-plugin]: ./available/grafana-v1-alpha.md\n[deploy-image]: ./available/deploy-image-plugin-v1-alpha.md\n[external-plugins]: ./extending/external-plugins.md"
  },
  {
    "path": "docs/book/src/plugins/to-add-optional-features.md",
    "content": "## To add optional features\n\nThe following plugins are useful to generate code and take advantage of optional features\n\n| Plugin                                              | Key                     | Description                                                                                                                                                                           |\n|-----------------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| [autoupdate.kubebuilder.io/v1-alpha][autoupdate]    | `autoupdate/v1-alpha`   | Optional helper which scaffolds a scheduled worker that helps keep your project updated with changes in the ecosystem, significantly reducing the burden of manual maintenance. |\n| [deploy-image.go.kubebuilder.io/v1-alpha][deploy]   | `deploy-image/v1-alpha` | Optional helper plugin which can be used to scaffold APIs and controller with code implementation to Deploy and Manage an Operand(image).                                             |\n| [grafana.kubebuilder.io/v1-alpha][grafana]          | `grafana/v1-alpha`      | Optional helper plugin which can be used to scaffold Grafana Manifests Dashboards for the default metrics which are exported by controller-runtime.                                   |\n| [helm.kubebuilder.io/v1-alpha][helm-v1alpha] (deprecated) | `helm/v1-alpha`         | **Deprecated** - Optional helper plugin which can be used to scaffold a Helm Chart to distribute the project under the `dist` directory. Use v2-alpha instead.                     |\n| [helm.kubebuilder.io/v2-alpha][helm-v2alpha]        | `helm/v2-alpha`         | Optional helper plugin which dynamically generates Helm charts from kustomize output, preserving all customizations                                                                     |\n\n[grafana]: ./available/grafana-v1-alpha.md\n[deploy]: ./available/deploy-image-plugin-v1-alpha.md\n[helm-v1alpha]: ./available/helm-v1-alpha.md\n[helm-v2alpha]: ./available/helm-v2-alpha.md\n[autoupdate]: ./available/autoupdate-v1-alpha.md"
  },
  {
    "path": "docs/book/src/plugins/to-be-extended.md",
    "content": "## To be extended\n\nThe following plugins are useful for other tools and [External Plugins][external-plugins] which are looking to extend the\nKubebuilder functionality.\n\nYou can use the kustomize plugin, which is responsible for scaffolding the\nkustomize files under `config/`. The base language plugins are responsible\nfor scaffolding the necessary Golang files, allowing you to create your\nown plugins for other languages (e.g., [Operator-SDK][sdk] enables\nusers to work with Ansible/Helm) or add additional functionality.\n\nFor example, [Operator-SDK][sdk] has a plugin which integrates the\nprojects with [OLM][olm] by adding its own features on top.\n\n| Plugin                                                 | Key                         | Description                                                                                                                                     |\n|--------------------------------------------------------|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|\n| [kustomize.common.kubebuilder.io/v2][kustomize-plugin] | `kustomize/v2` | Responsible for scaffolding all [kustomize][kustomize] files under the `config/` directory                                                      |\n| `base.go.kubebuilder.io/v4`                            | `base/v4`      | Responsible for scaffolding all files which specifically requires Golang. This plugin is used in the composition to create the plugin (`go/v4`) |\n\n[kustomize]: https://kustomize.io/\n[sdk]: https://github.com/operator-framework/operator-sdk\n[olm]: https://olm.operatorframework.io/\n[kustomize-plugin]: ./available/kustomize-v2.md\n[external-plugins]: ./extending/external-plugins.md\n"
  },
  {
    "path": "docs/book/src/plugins/to-scaffold-project.md",
    "content": "## To scaffold the projects\n\nThe following plugins are useful to scaffold the whole project with the tool.\n\n| Plugin                                                                   | Key     | Description                                                                                                                                                                   |\n|--------------------------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| [go.kubebuilder.io/v4 - (Default scaffold with Kubebuilder init)][go-v4] | `go/v4` | Scaffold composite by `base.go.kubebuilder.io/v4` and [kustomize.common.kubebuilder.io/v2][kustomize-v2]. Responsible for scaffolding Golang projects and its configurations. |\n\n[go-v4]: ./available/go-v4-plugin.md\n[kustomize-v2]: ./available/kustomize-v2.md"
  },
  {
    "path": "docs/book/src/quick-start.md",
    "content": "# Quick Start\n\nThis Quick Start guide will cover:\n\n- [Creating a project](#create-a-project)\n- [Creating an API](#create-an-api)\n- [Running locally](#test-it-out)\n- [Running in-cluster](#run-it-on-the-cluster)\n\n## Prerequisites\n\n- [go](https://go.dev/dl/) version v1.24.6+\n- [docker](https://docs.docker.com/install/) version 17.03+.\n- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n<aside class=\"note\">\n<h1>Versions Compatibility and Supportability</h1>\n\nPlease, ensure that you see the [guidance](./versions_compatibility_supportability.md).\n\n</aside>\n\n## Installation\n\nInstall [kubebuilder](https://sigs.k8s.io/kubebuilder):\n\n```bash\n# download kubebuilder and install locally.\ncurl -L -o kubebuilder \"https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)\"\nchmod +x kubebuilder && sudo mv kubebuilder /usr/local/bin/\n```\n\n<aside class=\"note\">\n<h1>Using the Master Branch</h1>\n\nYou can work with the master branch by cloning the repository and running `make install` to generate the binary.\nPlease follow the steps in the section **How to Build Kubebuilder Locally** from the [Contributing Guide](https://github.com/kubernetes-sigs/kubebuilder/blob/master/CONTRIBUTING.md#how-to-build-kubebuilder-locally).\n\n</aside>\n\n<aside class=\"note\">\n<h1>Enabling shell autocompletion</h1>\n\nKubebuilder provides autocompletion support via the command `kubebuilder completion <bash|fish|powershell|zsh>`, which can save you a lot of typing. For further information see the [completion](./reference/completion.md) document.\n\n</aside>\n\n## Create a Project\n\nCreate a directory, and then run the init command inside of it to initialize a new project. Follows an example.\n\n```bash\nmkdir -p ~/projects/guestbook\ncd ~/projects/guestbook\nkubebuilder init --domain my.domain --repo my.domain/guestbook\n```\n\n<aside class=\"note\">\n<h1>Developing in $GOPATH</h1>\n\nIf your project is initialized within [`GOPATH`][GOPATH-golang-docs], the implicitly called `go mod init` will interpolate the module path for you.\nOtherwise `--repo=<module path>` must be set.\n\nRead the [Go modules blogpost][go-modules-blogpost] if unfamiliar with the module system.\n\n</aside>\n\n## Create an API\n\nRun the following command to create a new API (group/version) as `webapp/v1` and the new Kind(CRD) `Guestbook` on it:\n\n```bash\nkubebuilder create api --group webapp --version v1 --kind Guestbook\n```\n\n<aside class=\"note\">\n<h1>Press Options</h1>\n\nIf you press `y` for Create Resource [y/n] and for Create Controller [y/n] then this will create the files `api/v1/guestbook_types.go` where the API is defined\nand the `internal/controller/guestbook_controller.go` where the reconciliation business logic is implemented for this Kind(CRD).\n\n</aside>\n\n**OPTIONAL:** Edit the API definition and the reconciliation business\nlogic. For more info see [Designing an API](/cronjob-tutorial/api-design.md) and [What's in\na Controller](cronjob-tutorial/controller-overview.md).\n\nIf you are editing the API definitions, generate the manifests such as Custom Resources (CRs) or Custom Resource Definitions (CRDs) using\n\n```bash\nmake manifests\n```\n\n<details><summary>Click here to see an example. <tt>(api/v1/guestbook_types.go)</tt></summary>\n<p>\n\n```go\n// GuestbookSpec defines the desired state of Guestbook\ntype GuestbookSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// Quantity of instances\n\t// +kubebuilder:validation:Minimum=1\n\t// +kubebuilder:validation:Maximum=10\n\tSize int32 `json:\"size\"`\n\n\t// Name of the ConfigMap for GuestbookSpec's configuration\n\t// +kubebuilder:validation:MaxLength=15\n\t// +kubebuilder:validation:MinLength=1\n\tConfigMapName string `json:\"configMapName\"`\n\n\t// +kubebuilder:validation:Enum=Phone;Address;Name\n\tType string `json:\"type,omitempty\"`\n}\n\n// GuestbookStatus defines the observed state of Guestbook\ntype GuestbookStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// PodName of the active Guestbook node.\n\tActive string `json:\"active\"`\n\n\t// PodNames of the standby Guestbook nodes.\n\tStandby []string `json:\"standby\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Cluster\n\n// Guestbook is the Schema for the guestbooks API\ntype Guestbook struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   GuestbookSpec   `json:\"spec,omitempty\"`\n\tStatus GuestbookStatus `json:\"status,omitempty\"`\n}\n```\n\n</p>\n</details>\n\n\n<aside class=\"note\">\n<h1> `+kubebuilder` markers </h1>\n\n`+kubebuilder` are [markers][markers] processed by [controller-gen][controller-gen]\nto generate CRDs and RBAC. Kubebuilder also provides [scaffolding markers][scaffolding-markers]\nto inject code into existing files and simplify common tasks. See `cmd/main.go` for examples.\n\n</aside>\n\n## Test It Out\n\nYou'll need a Kubernetes cluster to run against. You can use\n[KinD][kind] to get a local cluster for testing, or\nrun against a remote cluster.\n\n<aside class=\"note\">\n<h1>Context Used</h1>\n\nYour controller will automatically use the current context in your\nkubeconfig file (i.e. whatever cluster `kubectl cluster-info` shows).\n\n</aside>\n\nInstall the CRDs into the cluster:\n\n```bash\nmake install\n```\n\nFor quick feedback and code-level debugging, run your controller (this will run in the foreground, so switch to a new\nterminal if you want to leave it running):\n\n```bash\nmake run\n```\n\n## Install Instances of Custom Resources\n\nIf you pressed `y` for Create Resource [y/n] then you created a CR for your CRD in your\n`config/samples/` directory.\n\nEdit `config/samples/webapp_v1_guestbook.yaml` to contain a valid `spec`. For example:\n\n```yaml\n# ...\nspec:\n  foo: bar\n```\n\nHint: \"foo\" is a string field defined in `api/v1/guestbook_types.go`:\n\n```go\n// foo is an example field of Guestbook. Edit guestbook_types.go to remove/update\n// +optional\nFoo *string `json:\"foo,omitempty\"`\n```\n\n```bash\nkubectl apply -k config/samples/\n```\n\nYou can have a look at your applied resource now:\n\n```bash\nkubectl get guestbooks.webapp.my.domain guestbook-sample -o yaml\n```\n\n## Run It On the Cluster\n\nWhen your controller is ready to be packaged and tested in other clusters.\n\nBuild and push your image to the location specified by `IMG`:\n\n```bash\nmake docker-build docker-push IMG=<some-registry>/<project-name>:tag\n```\n\nDeploy the controller to the cluster with image specified by `IMG`:\n\n```bash\nmake deploy IMG=<some-registry>/<project-name>:tag\n```\n\n<aside class=\"note\">\n<h1>Registry Permission</h1>\n\nThis image ought to be published in the personal registry you specified. And it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don't work.\n\nConsider incorporating [Kind][kind] into your workflow for a faster, more efficient local development and CI experience.\nNote that, if you're using a [Kind][kind] cluster, there's no need to push your image to a remote container registry.\nYou can directly load your local image into your specified [Kind][kind] cluster:\n\n```bash\nkind load docker-image <your-image-name>:tag --name <your-kind-cluster-name>\n```\n\nIt is highly recommended to use [Kind][kind] for development purposes and CI.\nTo know more, see: [Using Kind For Development Purposes and CI](./reference/kind.md)\n\n<h1>RBAC errors</h1>\n\nIf you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin. See [Prerequisites for using Kubernetes RBAC on GKE cluster v1.11.x and older][pre-rbc-gke] which may be your case.\n\n</aside>\n\n## Uninstall CRDs\n\nTo delete your CRDs from the cluster:\n\n```bash\nmake uninstall\n```\n\n## Undeploy controller\n\nUndeploy the controller to the cluster:\n\n```bash\nmake undeploy\n```\n## Using Plugins\n\nKubebuilder design is based on [Plugins][plugins] and you can use\n[available plugins][available-plugins] to add optional features to your project.\n\n<aside class=\"note\">\n<h1>References and Examples</h1>\n\nUse the [Deploy Image Plugin (deploy-image/v1-alpha)][deploy-image-v1-alpha] as a reference when creating your project. It follows Kubernetes conventions and recommended good practices. For example:\n\n```bash\nkubebuilder create api --group webapp --version v1alpha1 --kind Busybox --image=busybox:1.36.1 --plugins=\"deploy-image/v1-alpha\"\n```\n\n</aside>\n\n<aside class=\"note\">\n<h1> Keeping your project up to date with ecosystem changes </h1>\n\nUse [AutoUpdate Plugin][autoupdate-v1-alpha] to keep your project\naligned with the latest ecosystem changes. When a new release is available,\nit automatically opens an issue with a PR comparison link so you can review and update easily.\n\n```bash\nkubebuilder edit --plugins=\"autoupdate/v1-alpha\"\n```\n\n</aside>\n\n## Next Steps\n\n- [Getting Started Guide][getting-started] (~30 min) - build a solid foundation\n- [CronJob Tutorial][cronjob-tutorial] - learn by building a demo project\n- [Groups, Versions, and Kinds][gkv-doc] - understand API design concepts\n\n[pre-rbc-gke]: https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control#iam-rolebinding-bootstrap\n[cronjob-tutorial]: https://book.kubebuilder.io/cronjob-tutorial/cronjob-tutorial.html\n[GOPATH-golang-docs]: https://go.dev/doc/code.html#GOPATH\n[go-modules-blogpost]: https://blog.go.dev/using-go-modules\n[architecture-concept-diagram]: architecture.md\n[kustomize]: https://github.com/kubernetes-sigs/kustomize\n[getting-started]: getting-started.md\n[plugins]: plugins/plugins.md\n[available-plugins]: plugins/available-plugins.md\n[envtest]: ./reference/envtest.md\n[autoupdate-v1-alpha]: plugins/available/autoupdate-v1-alpha.md\n[deploy-image-v1-alpha]: plugins/available/deploy-image-plugin-v1-alpha.md\n[gkv-doc]: cronjob-tutorial/gvks.md\n[kind]: https://sigs.k8s.io/kind\n[markers]: reference/markers.md\n[controller-gen]: https://sigs.k8s.io/controller-tools/cmd/controller-gen\n[scaffolding-markers]: reference/markers/scaffold.md\n[ai-gh-models]: https://docs.github.com/en/github-models/about-github-models\n"
  },
  {
    "path": "docs/book/src/reference/admission-webhook.md",
    "content": "# Admission Webhooks\n\n[Admission webhooks](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#what-are-admission-webhooks) are HTTP callbacks that receive admission requests, process\nthem and return admission responses.\n\nKubernetes provides the following types of admission webhooks:\n\n- **Mutating Admission Webhook**:\nThese can mutate the object while it's being created or updated, before it gets\nstored. It can be used to default fields in a resource requests, e.g. fields in\nDeployment that are not specified by the user. It can be used to inject sidecar\ncontainers.\n\n- **Validating Admission Webhook**:\nThese can validate the object while it's being created or updated, before it gets\nstored. It allows more complex validation than pure schema-based validation.\ne.g. cross-field validation and pod image whitelisting.\n\nThe apiserver by default doesn't authenticate itself to the webhooks. However,\nif you want to authenticate the clients, you can configure the apiserver to use\nbasic auth, bearer token, or a cert to authenticate itself to the webhooks.\nYou can find detailed steps\n[here](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers).\n\n<aside class=\"note\">\n<H1>Execution Order</H1>\n\n**Validating webhooks run after all mutating webhooks**, so you don't need to worry about another webhook changing an\nobject after your validation has accepted it.\n\n</aside>\n\n## Custom Webhook Paths\n\nBy default, Kubebuilder generates webhook paths based on the resource's group, version, and kind. For example:\n- Mutating webhook for `batch/v1/CronJob`: `/mutate-batch-v1-cronjob`\n- Validating webhook for `batch/v1/CronJob`: `/validate-batch-v1-cronjob`\n\nYou can specify custom paths for webhooks using dedicated flags:\n\n```bash\n# Custom path for defaulting webhook\nkubebuilder create webhook --group batch --version v1 --kind CronJob \\\n  --defaulting --defaulting-path=/my-custom-mutate-path\n\n# Custom path for validation webhook\nkubebuilder create webhook --group batch --version v1 --kind CronJob \\\n  --programmatic-validation --validation-path=/my-custom-validate-path\n\n# Both webhooks with different custom paths\nkubebuilder create webhook --group batch --version v1 --kind CronJob \\\n  --defaulting --programmatic-validation \\\n  --defaulting-path=/custom-mutate --validation-path=/custom-validate\n```\n\n<aside class=\"note\">\n<h1>Version Requirements</h1>\n\nCustom webhook paths require **controller-runtime v0.21+**. In earlier versions (< `v0.21`), the webhook path follows a\nfixed pattern based on the resource's group, version, and kind, and cannot be customized.\n</aside>\n\n\n## Handling Resource Status in Admission Webhooks\n\n<aside class=\"warning\">\n<H1>Modify status</H1>\n\n**You cannot modify or default the status of a resource using a mutating admission webhook**.\nSet initial status in your controller when you first see a new object.\n\n</aside>\n\n### Understanding Why:\n\n#### Mutating Admission Webhooks\n\nMutating Admission Webhooks are primarily designed to intercept and modify requests concerning the creation,\nmodification, or deletion of objects. Though they possess the capability to modify an object's specification,\ndirectly altering its status isn't deemed a standard practice,\noften leading to unintended results.\n\n```go\n// MutatingWebhookConfiguration allows for modification of objects.\n// However, direct modification of the status might result in unexpected behavior.\ntype MutatingWebhookConfiguration struct {\n    ...\n}\n```\n\n#### Setting Initial Status\n\nFor those diving into custom controllers for custom resources, it's imperative to grasp the concept of setting an\ninitial status. This initialization typically takes place within the controller itself. The moment the controller\nidentifies a new instance of its managed resource, primarily through a watch mechanism, it holds the authority\nto assign an initial status to that resource.\n\n```go\n// Custom controller's reconcile function might look something like this:\nfunc (r *ReconcileMyResource) Reconcile(request reconcile.Request) (reconcile.Result, error) {\n    // ...\n    // Upon discovering a new instance, set the initial status\n    instance.Status = SomeInitialStatus\n    // ...\n}\n```\n\n#### Status Subresource\n\nDelving into Kubernetes custom resources, a clear demarcation exists between the spec (depicting the desired state)\nand the status (illustrating the observed state). Activating the /status subresource for a custom resource definition\n(CRD) bifurcates the `status` and `spec`, each assigned to its respective API endpoint.\nThis separation ensures that changes introduced by users, such as modifying the spec, and system-driven updates,\nlike status alterations, remain distinct. Leveraging a mutating webhook to tweak the status during a spec-modifying\noperation might not pan out as expected, courtesy of this isolation.\n\n```yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: myresources.mygroup.mydomain\nspec:\n  ...\n  subresources:\n    status: {} # Enables the /status subresource\n```\n\n#### Conclusion\n\nWhile certain edge scenarios might allow a mutating webhook to seamlessly modify the status, treading this path isn't a\nuniversally acclaimed or recommended strategy. Entrusting the controller logic with status updates remains the\nmost advocated approach.\n"
  },
  {
    "path": "docs/book/src/reference/alpha_commands.md",
    "content": "# Alpha Commands\n\nKubebuilder provides experimental **alpha commands** to assist with advanced operations such as\nproject migration and scaffold regeneration.\n\nThese commands are designed to simplify tasks that were previously manual and error-prone\nby automating or partially automating the process.\n\n<aside class=\"warning\">\n    <h3>Alpha commands are experimental</h3>\n\nAlpha commands are under active development and may change or be removed in future releases.\nThey make local changes to your project and may delete files during execution.\n\nAlways ensure your work is committed or backed up before using them.\n</aside>\n\nThe following alpha commands are currently available:\n\n- [`alpha generate`](./../reference/commands/alpha_generate.md) — Re-scaffold the project using the installed CLI version\n- [`alpha update`](./../reference/commands/alpha_update.md) — Automate the migration process via 3-way merge using scaffold snapshots\n\nFor more information, see each command's dedicated documentation.\n"
  },
  {
    "path": "docs/book/src/reference/artifacts.md",
    "content": "# Artifacts\n\nTo test your controllers, you will need to use the tarballs containing the required binaries:\n\n```shell\n./bin/k8s/\n└── 1.25.0-darwin-amd64\n    ├── etcd\n    ├── kube-apiserver\n    └── kubectl\n```\n\nThese tarballs are released by [controller-tools](https://github.com/kubernetes-sigs/controller-tools),\nand you can find the list of available versions at: [envtest-releases.yaml](https://github.com/kubernetes-sigs/controller-tools/blob/main/envtest-releases.yaml).\n\nWhen you run `make envtest` or `make test`, the necessary tarballs are downloaded and properly\nconfigured for your project.\n\n<aside class=\"note\">\n<h1>Setup ENV TEST tool</h1>\n\nTo learn more about the tooling used to configure ENVTEST, which is utilized in the `setup-envtest`\ntarget in the Makefile of projects built with Kubebuilder, see the [README](https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md)\nof its tooling. Additionally, you can find more information by reviewing the Kubebuilder [ENVTEST][env-test-doc] documentation.\n\n</aside>\n\n\n<aside class=\"warning\">\n    <h3>IMPORTANT: Action Required: Ensure that you no longer use https://storage.googleapis.com/kubebuilder-tools </h3>\n\n**Artifacts provided under [https://storage.googleapis.com/kubebuilder-tools](https://storage.googleapis.com/kubebuilder-tools) are deprecated and Kubebuilder maintainers are no longer able to support, build, or ensure the promotion of these artifacts.**\n\nYou will find the [ENVTEST][env-test-doc] binaries available in the new location from k8s release `1.28`, see: [https://github.com/kubernetes-sigs/controller-tools/blob/main/envtest-releases.yaml](https://github.com/kubernetes-sigs/controller-tools/blob/main/envtest-releases.yaml).\nAlso, binaries to test your controllers after k8s `1.29.3` will no longer be found in the old location.\n\n**New binaries are only promoted in the new location**.\n\n**You should ensure that your projects are using the new location.**\nPlease ensure you use `setup-envtest` from the controller-runtime `release v0.19.0` to be able to download those.\n**This update is fully transparent for Kubebuilder users.**\n\nThe artefacts for [ENVTEST][env-test-doc] k8s `1.31` are exclusively available at: [Controller Tools Releases][controller-gen].\n\nYou can refer to the Makefile of the Kubebuilder scaffold and observe that the envtest setup is consistently aligned across all controller-runtime releases. Starting from `release-0.19`, it is configured to automatically download the artefact from the correct location, **ensuring that kubebuilder users are not impacted.**\n\n```shell\n## Tool Binaries\n..\nENVTEST ?= $(LOCALBIN)/setup-envtest\n...\n\n## Tool Versions\n...\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell go list -m -f \"{{ .Version }}\" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf \"release-%d.%d\", $$2, $$3}')\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell go list -m -f \"{{ .Version }}\" k8s.io/api | awk -F'[v.]' '{printf \"1.%d\", $$3}')\n...\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n```\n\n</aside>\n\n[env-test-doc]: ./envtest.md\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[controller-gen]: https://github.com/kubernetes-sigs/controller-tools/releases\n"
  },
  {
    "path": "docs/book/src/reference/commands/alpha_generate.md",
    "content": "# Regenerate your project with (`alpha generate`)\n\n## Overview\n\nThe `kubebuilder alpha generate` command re-scaffolds your project using the currently installed\nCLI and plugin versions.\n\nIt regenerates the full scaffold based on the configuration specified in your [PROJECT][project-config] file.\nThis allows you to apply the latest layout changes, plugin features, and code generation improvements introduced\nin newer Kubebuilder releases.\n\nYou may choose to re-scaffold the project in-place (overwriting existing files) or in a separate\ndirectory for diff-based inspection and manual integration.\n\n<aside class=\"warning\">\n    <h3>Deletes files during scaffold regeneration</h3>\nWhen executed in-place, this command deletes all files except `.git` and `PROJECT`.\n\nAlways back up your project or use version control before running this command.\n</aside>\n\n## When to Use It?\n\nYou can use `kubebuilder alpha generate` to upgrade your project scaffold when new changes are introduced\nin Kubebuilder. This includes updates to plugins (for example, `go.kubebuilder.io/v3` → `go.kubebuilder.io/v4`)\nor the CLI releases (for example, 4.3.1 → latest) .\n\nThis command is helpful when you want to:\n\n- Update your project to use the latest layout or plugin version\n- Regenerate your project scaffold to include recent changes\n- Compare the current scaffold with the latest and apply updates manually\n- Create a clean scaffold for reviewing or testing changes\n\nUse this command when you want full control of the upgrade process.\nIt is also useful if your project was created with an older CLI version and does not support `alpha update`.\n\nThis approach allows you to compare changes between your current branch and upstream\nscaffold updates (e.g., from the main branch), and helps you overlay custom code atop the new scaffold.\n\n<aside class=\"note tip\">\n<h1>Looking for a more automated migration?</h1>\n\nIf you want to upgrade your project scaffold with less manual work,\ntry [`kubebuilder alpha update`](./alpha_update.md).\n\nIt uses a 3-way merge to keep your code and apply the latest scaffold changes automatically.\nUse `alpha generate` if `alpha update` is not available for your project yet\nor if you prefer to handle changes manually.\n\n</aside>\n\n## How to Use It?\n\n### Upgrade your current project to CLI version installed (i.e. latest scaffold)\n\n```sh\nkubebuilder alpha generate\n```\n\nAfter running this command, your project will be re-scaffolded in place.\nYou can then compare the local changes with your main branch to see what was updated,\nand re-apply your custom code on top as needed.\n\n### Generate Scaffold to a New Directory\n\nUse the `--input-dir` and `--output-dir` flags to specify input and output paths.\n\n```sh\nkubebuilder alpha generate \\\n  --input-dir=/path/to/existing/project \\\n  --output-dir=/path/to/new/project\n```\n\nAfter running the command, you can inspect the generated scaffold in the specified output directory.\n\n### Flags\n\n| Flag            | Description                                                                 |\n|------------------|-----------------------------------------------------------------------------|\n| `--input-dir`    | Path to the directory containing the `PROJECT` file. Defaults to CWD. Deletes all files except `.git` and `PROJECT`. |\n| `--output-dir`   | Directory where the new scaffold will be written. If unset, re-scaffolds in-place. |\n| `--plugins`      | Plugin keys to use for this generation.                                     |\n| `-h, --help`     | Show help for this command.                                                 |\n\n\n## Further Resources\n\n- [Video demo on how it works](https://youtu.be/7997RIbx8kw?si=ODYMud5lLycz7osp)\n- [Design proposal documentation](../../../../../designs/helper_to_upgrade_projects_by_rescaffolding.md)\n\n[example]: ../../../../../testdata/project-v4-with-plugins/PROJECT\n[project-config]: ../../reference/project-config.md"
  },
  {
    "path": "docs/book/src/reference/commands/alpha_update.md",
    "content": "# Update Your Project with (`alpha update`)\n\n## Overview\n\n`kubebuilder alpha update` upgrades your project’s scaffold to a newer Kubebuilder release using a **3-way Git merge**. It rebuilds clean scaffolds for the old and new versions, merges your current code into the new scaffold, and gives you a reviewable output branch.\nIt takes care of the heavy lifting so you can focus on reviewing and resolving conflicts,\nnot re-applying your code.\n\nBy default, the final result is **squashed into a single commit** on a dedicated output branch.\nIf you prefer to keep the full history (no squash), use `--show-commits`.\n\n<aside class=\"note\">\n<H1> Automate this process </H1>\n\nYou can reduce the burden of keeping your project up to date by using the\n[AutoUpdate Plugin][autoupdate-plugin] which\nautomates the process of running `kubebuilder alpha update` on a schedule\nworkflow when new Kubebuilder releases are available.\n\nMoreover, you will be able to get help from [AI models][ai-gh-models] to understand what changes are needed to keep your project up to date\nand how to solve conflicts if any are faced.\n\n</aside>\n\n## When to Use It\n\nUse this command when you:\n\n- Want to move to a newer Kubebuilder version or plugin layout\n- Want to review scaffold changes on a separate branch\n- Want to focus on resolving merge conflicts (not re-applying your custom code)\n\n## How It Works\n\nYou tell the tool the **new version**, and which branch has your project.\nIt rebuilds both scaffolds, merges your code into the new one with a **3-way merge**,\nand gives you an output branch you can review and merge safely.\nYou decide if you want one clean commit, the full history, or an auto-push to remote.\n\n### Step 1: Detect versions\n- It looks at your `PROJECT` file or the flags you pass.\n- Decides which **old version** you are coming from by reading the `cliVersion` field in the `PROJECT` file (if available).\n- Figures out which **new version** you want (defaults to the latest release).\n- Chooses which branch has your current code (defaults to `main`).\n\n### Step 2: Create scaffolds\nThe command creates three temporary branches:\n- **Ancestor**: a clean project scaffold from the **old version**.\n- **Original**: a snapshot of your **current code**.\n- **Upgrade**: a clean scaffold from the **new version**.\n\n### Step 3: Do a 3-way merge\n- Merges **Original** (your code) into **Upgrade** (the new scaffold) using Git’s **3-way merge**.\n- This keeps your customizations while pulling in upstream changes.\n- If conflicts happen:\n    - **Default** → stop and let you resolve them manually.\n    - **With `--force`** → continue and commit even with conflict markers. **(ideal for automation)**\n- Runs `make manifests generate fmt vet lint-fix` to tidy things up.\n\n### Step 4: Write the output branch\n- By default, everything is **squashed into one commit** on a safe output branch:\n  `kubebuilder-update-from-<from-version>-to-<to-version>`.\n- You can change the behavior:\n    - `--show-commits`: keep the full history.\n    - `--restore-path`: in squash mode, restore specific files (like CI configs) from your base branch.\n    - `--output-branch`: pick a custom branch name.\n    - `--merge-message`: customize the commit message for clean merges.\n    - `--conflict-message`: customize the commit message when conflicts occur.\n    - `--push`: push the result to `origin` automatically.\n    - `--git-config`: sets git configurations.\n    - `--open-gh-issue`: create a GitHub issue with a checklist and compare link (requires `gh`).\n    - `--use-gh-models`: add an AI overview **comment** to that issue using `gh models`\n\n### Step 5: Cleanup\n- Once the output branch is ready, all the temporary working branches are deleted.\n- You are left with one clean branch you can test, review, and merge back into your main branch.\n\n## How to Use It (commands)\n\nRun from your project root:\n\n```shell\nkubebuilder alpha update\n```\n\nPin versions and base branch:\n\n```shell\nkubebuilder alpha update \\\n--from-version v4.5.2 \\\n--to-version   v4.6.0 \\\n--from-branch  main\n```\nAutomation-friendly (proceed even with conflicts):\n\n```shell\nkubebuilder alpha update --force\n```\n\nKeep full history instead of squashing:\n```\nkubebuilder alpha update --from-version v4.5.0 --to-version v4.7.0 --force --show-commits\n```\n\nDefault squash but **preserve** CI/workflows from the base branch:\n\n```shell\nkubebuilder alpha update --force \\\n--restore-path .github/workflows \\\n--restore-path docs\n```\n\nUse a custom output branch name:\n\n```shell\nkubebuilder alpha update --force \\\n--output-branch upgrade/kb-to-v4.7.0\n```\n\nRun update and push the result to origin:\n\n```shell\nkubebuilder alpha update --from-version v4.6.0 --to-version v4.7.0 --force --push\n```\n\nCustomize commit messages:\n\n```shell\nkubebuilder alpha update --force \\\n--merge-message \"chore: upgrade kubebuilder scaffold\" \\\n--conflict-message \"chore: upgrade with conflicts - manual review needed\"\n```\n\n## Handling Conflicts (`--force` vs default)\n\nWhen you use `--force`, Git finishes the merge even if there are conflicts.\nThe commit will include markers like:\n\n```shell\n<<<<<<< HEAD\nYour changes\n=======\nIncoming changes\n>>>>>>> (original)\n```\n\nThis allows you to run the command in CI or cron jobs without manual intervention.\n\n- Without `--force`: the command stops on the merge branch and prints guidance; no commit is created.\n- With `--force`: the merge is committed (merge or output branch) and contains the markers.\n\nAfter you fix conflicts, always run:\n\n```shell\nmake manifests generate fmt vet lint-fix\n# or\nmake all\n```\n\n## Using with GitHub Issues (`--open-gh-issue`) and AI (`--use-gh-models`) assistance\n\nPass `--open-gh-issue` to have the command create a GitHub **Issue** in your repository\nto assist with the update. Also, if you also pass `--use-gh-models`, the tool posts a follow-up comment\non that Issue with an AI-generated overview of the most important changes plus brief conflict-resolution\nguidance.\n\n### Examples\n\nCreate an Issue with a compare link:\n```shell\nkubebuilder alpha update --open-gh-issue\n```\n\nCreate an Issue **and** add an AI summary:\n```shell\nkubebuilder alpha update --open-gh-issue --use-gh-models\n```\n\n### What you’ll see\n\nThe command opens an Issue that links to the diff so you can create the PR and review it, for example:\n\n<img width=\"638\" height=\"482\" alt=\"Example Issue\" src=\"https://github.com/user-attachments/assets/589fd16b-7709-4cd5-b169-fd53d69790d4\" />\n\nWith `--use-gh-models`, an AI comment highlights key changes and suggests how to resolve any conflicts:\n\n<img width=\"740\" height=\"425\" alt=\"Comment\" src=\"https://github.com/user-attachments/assets/fb5f214e-be0e-43b8-a3fb-b5744ac8f66e\" />\n\nMoreover, AI models are used to help you understand what changes are needed to keep your project up to date,\nand to suggest resolutions if conflicts are encountered, as in the following example:\n\n### Automation\n\nThis integrates cleanly with automation. The [`autoupdate.kubebuilder.io/v1-alpha`][autoupdate-plugin] plugin can scaffold a GitHub Actions workflow that runs the command on a schedule (e.g., weekly). When a new Kubebuilder release is available, it opens an Issue with a compare link so you can create the PR and review it.\n\n## Changing Extra Git configs only during the run (does not change your ~/.gitconfig)_\n\nBy default, `kubebuilder alpha update` applies safe Git configs:\n`merge.renameLimit=999999`, `diff.renameLimit=999999`, `merge.conflictStyle=merge`\nYou can add more, or disable them.\n\n- **Add more on top of defaults**\n```shell\nkubebuilder alpha update \\\n  --git-config rerere.enabled=true\n```\n\n- **Disable defaults entirely**\n```shell\nkubebuilder alpha update --git-config disable\n```\n\n- **Disable defaults and set your own**\n\n```shell\nkubebuilder alpha update \\\n  --git-config disable \\\n  --git-config rerere.enabled=true\n```\n\n<aside class=\"warning\">\n    <h3>You might need to upgrade your project first</h3>\n\nThis command uses `kubebuilder alpha generate` under the hood.\nWe support projects created with <strong>v4.5.0+</strong>.\nIf yours is older, first run `kubebuilder alpha generate` once to modernize the scaffold.\nAfter that, you can use `kubebuilder alpha update` for future upgrades.\n\nProjects created with **Kubebuilder v4.6.0+** include `cliVersion` in the `PROJECT` file.\nWe use that value to pick the correct CLI for re-scaffolding.\n\n</aside>\n\n## Flags\n\n| Flag                         | Description                                                                                                                                                                                                                             |\n|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--conflict-message`         | Custom commit message for merges with conflicts. Defaults to `:warning: chore(kubebuilder): update scaffold (manual conflict resolution) <from> -> <to>`.                                                                               |\n| `--force`          | Continue even if merge conflicts happen. Conflicted files are committed with conflict markers (CI/cron friendly).                                                                                                                       |\n| `--from-branch`    | Git branch that holds your current project code. Defaults to `main`.                                                                                                                                                                    |\n| `--from-version`   | Kubebuilder release to update **from** (e.g., `v4.6.0`). If unset, read from the `PROJECT` file when possible.                                                                                                                          |\n| `--git-config`     | Repeatable. Pass per-invocation Git config as `-c key=value`. **Default** (if omitted): `-c merge.renameLimit=999999 -c diff.renameLimit=999999`. Your configs are applied on top. To disable defaults, include `--git-config disable`. |\n| `--merge-message`            | Custom commit message for successful merges (no conflicts). Defaults to `chore(kubebuilder): update scaffold <from> -> <to>`.                                                                                                           |\n| `--open-gh-issue`  | Create a GitHub issue with a pre-filled checklist and compare link after the update completes (requires `gh`).                                                                                                                          |\n| `--output-branch`  | Name of the output branch. Default: `kubebuilder-update-from-<from-version>-to-<to-version>`.                                                                                                                                           |\n| `--push`           | Push the output branch to the `origin` remote after the update completes.                                                                                                                                                               |\n| `--restore-path`   | Repeatable. Paths to preserve from the base branch when squashing (e.g., `.github/workflows`). **Not supported** with `--show-commits`.                                                                                                 |\n| `--show-commits`   | Keep full history (do not squash). **Not compatible** with `--restore-path`.                                                                                                                                                            |\n| `--to-version`     | Kubebuilder release to update **to** (e.g., `v4.7.0`). If unset, defaults to the latest available release.                                                                                                                              |\n| `--use-gh-models`  | Post an AI overview as an issue comment using `gh models`. Requires `gh` + `gh-models` extension. Effective only when `--open-gh-issue` is also set.                                                                                    |\n| `-h, --help`       | Show help for this command.                                                                                                                                                                                                             |\n\n## Demonstration\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/J8zonID__8k?si=WC-FXOHX0mCjph71\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n\n<aside class=\"note\">\n<h1>About this demo</h1>\n\nThis video was recorded with Kubebuilder release `v7.0.1`.\nSince then, the command has been improved,\nso the current behavior may differ slightly from what is shown in the demo.\n\n</aside>\n\n## Further Resources\n\n- [AutoUpdate Plugin][autoupdate-plugin]\n- [Design proposal for update automation][design-proposal]\n- [Project configuration reference][project-config]\n\n[project-config]: ../../reference/project-config.md\n[autoupdate-plugin]: ./../../plugins/available/autoupdate-v1-alpha.md\n[design-proposal]: ./../../../../../designs/update_action.md\n[ai-gh-models]: https://docs.github.com/en/github-models/about-github-models\n"
  },
  {
    "path": "docs/book/src/reference/completion.md",
    "content": "# Enabling shell autocompletion\nThe Kubebuilder completion script can be generated with the command `kubebuilder completion [bash|fish|powershell|zsh]`.\nNote that sourcing the completion script in your shell enables Kubebuilder autocompletion.\n\n## Bash\n\n<aside class=\"note\">\n<h1>Prerequisites for Bash</h1>\n\nThe completion Bash script depends on [bash-completion](https://github.com/scop/bash-completion), which means that you have to install this software first (you can test if you have bash-completion already installed). Also, ensure that your Bash version is 4.1+.\n\n```bash\n$ bash --version\n```\n\n</aside>\n\n- Check that bash is an available shell:\n\n    ```bash\n    cat /etc/shells | grep '^.*/bash'\n    ```\n\n- If not, add bash to `/etc/shells`. For example, if bash is at `/usr/local/bin/bash`:\n\n    ```bash\n    echo \"/usr/local/bin/bash\" >> /etc/shells\n    ```\n\n- Make sure the current user uses bash as their shell.\n\n    ```bash\n    chsh -s /usr/local/bin/bash\n    ```\n\n- Add following content to `~/.bash_profile` or `~/.bashrc`\n\n    ```bash\n    # kubebuilder autocompletion\n    if [ -f /usr/local/share/bash-completion/bash_completion ]; then\n        . /usr/local/share/bash-completion/bash_completion\n    fi\n        . <(kubebuilder completion bash)\n    ```\n\n- Restart terminal for the changes to be reflected or `source` the changed bash file.\n\n    ```bash\n    . ~/.bash_profile\n    ```\n\n## Zsh\n\nFollow a similar protocol for `zsh` completion.\n\n## Fish\n\n```\nsource (kubebuilder completion fish | psub)\n```\n"
  },
  {
    "path": "docs/book/src/reference/controller-gen.md",
    "content": "# controller-gen CLI\n\nKubebuilder makes use of a tool called\n[controller-gen](https://sigs.k8s.io/controller-tools/cmd/controller-gen)\nfor generating utility code and Kubernetes YAML.  This code and config\ngeneration is controlled by the presence of special [\"marker\ncomments\"](/reference/markers.md) in Go code.\n\ncontroller-gen is built out of different \"generators\" (which specify what\nto generate) and \"output rules\" (which specify how and where to write the\nresults).\n\nBoth are configured through command line options specified in [marker\nformat](/reference/markers.md).\n\nFor instance, the following command:\n\n```shell\ncontroller-gen paths=./... crd:trivialVersions=true rbac:roleName=controller-perms output:crd:artifacts:config=config/crd/bases\n```\n\ngenerates CRDs and RBAC, and specifically stores the generated CRD YAML in\n`config/crd/bases`.  For the RBAC, it uses the default output rules\n(`config/rbac`).  It considers every package in the current directory tree\n(as per the normal rules of the go `...` wildcard).\n\n## Generators\n\nEach different generator is configured through a CLI option.  Multiple\ngenerators may be used in a single invocation of `controller-gen`.\n\n{{#markerdocs CLI: generators}}\n\n## Output Rules\n\nOutput rules configure how a given generator outputs its results. There is\nalways one global \"fallback\" output rule (specified as `output:<rule>`),\nplus per-generator overrides (specified as `output:<generator>:<rule>`).\n\n<aside class=\"note\">\n\n<h1>Default Rules</h1>\n\nWhen no fallback rule is specified manually, a set of default\nper-generator rules are used which result in YAML going to\n`config/<generator>`, and code staying where it belongs.\n\nThe default rules are equivalent to\n`output:<generator>:artifacts:config=config/<generator>` for each\ngenerator.\n\nWhen a \"fallback\" rule is specified, that'll be used instead of the\ndefault rules.\n\nFor example, if you specify `crd rbac:roleName=controller-perms\noutput:crd:stdout`, you'll get CRDs on standard out, and rbac in a file in\n`config/rbac`. If you were to add in a global rule instead, like `crd\nrbac:roleName=controller-perms output:crd:stdout output:none`, you'd get\nCRDs to standard out, and everything else to /dev/null, because we've\nexplicitly specified a fallback.\n\n</aside>\n\nFor brevity, the per-generator output rules (`output:<generator>:<rule>`)\nare omitted below.  They are equivalent to the global fallback options\nlisted here.\n\n{{#markerdocs CLI: output rules (optionally as output:<generator>:...)}}\n\n## Other Options\n\n{{#markerdocs CLI: generic}}\n"
  },
  {
    "path": "docs/book/src/reference/crd-scope.md",
    "content": "# CRD Scope\n\nThis document explains CustomResourceDefinition (CRD) scope in Kubernetes: how CRDs can be defined as namespace-scoped or cluster-scoped resources.\n\n<aside class=\"note\">\n<h1>CRD Scope vs Manager Scope</h1>\n\nCRD scope is independent from manager scope. See [Understanding Scopes](./scopes.md) for an explanation of how these two concepts differ.\n</aside>\n\n## Overview\n\nCRD scope determines the visibility and availability of custom resources:\n\n| Scope | Description | Example Resources |\n|-------|-------------|-------------------|\n| **Namespace-scoped** (default) | Resources exist within a specific namespace | Deployments, Services, ConfigMaps, Pods |\n| **Cluster-scoped** | Resources are global across the entire cluster | Nodes, ClusterRoles, Namespaces, PersistentVolumes |\n\n## Namespace-Scoped CRDs (Default)\n\nBy default, Kubebuilder creates namespace-scoped CRDs:\n\n```bash\nkubebuilder create api --group cache --version v1alpha1 --kind Memcached\n```\n\nGenerated CRD manifest:\n\n```yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: memcacheds.cache.example.com\nspec:\n  scope: Namespaced  # Default\n  group: cache.example.com\n  names:\n    kind: Memcached\n    plural: memcacheds\n  versions:\n  - name: v1alpha1\n    # ...\n```\n\nCustom resources are created in specific namespaces:\n\n```bash\nkubectl apply -f memcached.yaml -n my-namespace\nkubectl get memcacheds -n my-namespace\n```\n\n**When to use:**\n- Resources tied to specific applications, teams, or tenants\n- Multi-tenant environments where isolation is required\n- Most application-level resources\n\n**Considerations:**\n- Testing new CRD versions requires proper versioning and conversion strategies\n- Conversion webhooks must account for namespace scope\n- Facilitates controlled rollout within specific namespaces\n\n## Cluster-Scoped CRDs\n\nCluster-scoped CRDs create resources that are global across the entire cluster.\n\n### Creating Cluster-Scoped CRDs\n\nWhen creating the API, use the `--namespaced=false` flag:\n\n```bash\nkubebuilder create api --group infrastructure --version v1 --kind Database --namespaced=false\n```\n\nGenerated CRD manifest:\n\n```yaml\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: databases.infrastructure.example.com\nspec:\n  scope: Cluster  # Cluster-scoped\n  group: infrastructure.example.com\n  names:\n    kind: Database\n    plural: databases\n  versions:\n  - name: v1\n    # ...\n```\n\nCustom resources are cluster-wide (no namespace):\n\n```bash\nkubectl apply -f database.yaml\nkubectl get databases  # No namespace needed\n```\n\n**When to use:**\n- Resources that are global to the cluster (infrastructure, configuration)\n- Resources that need to be accessible from all namespaces\n- Resources that manage cluster-level concerns\n\n**Examples:**\n- Infrastructure configurations (cloud provider settings, cluster DNS)\n- Global policies or quotas\n- Cross-namespace resource aggregation\n\n## Changing CRD Scope\n\n### For Existing APIs\n\nAfter creating an API, you can change its scope using the `+kubebuilder:resource:scope` marker:\n\n**For cluster-scoped:**\n\n```go\n//+kubebuilder:object:root=true\n//+kubebuilder:subresource:status\n//+kubebuilder:resource:scope=Cluster\n\n// Database is the Schema for the databases API\ntype Database struct {\n    metav1.TypeMeta   `json:\",inline\"`\n    metav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n    Spec   DatabaseSpec   `json:\"spec,omitempty\"`\n    Status DatabaseStatus `json:\"status,omitempty\"`\n}\n```\n\n**For namespace-scoped:**\n\n```go\n//+kubebuilder:object:root=true\n//+kubebuilder:subresource:status\n//+kubebuilder:resource:scope=Namespaced\n\n// Memcached is the Schema for the memcacheds API\ntype Memcached struct {\n    metav1.TypeMeta   `json:\",inline\"`\n    metav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n    Spec   MemcachedSpec   `json:\"spec,omitempty\"`\n    Status MemcachedStatus `json:\"status,omitempty\"`\n}\n```\n\nAfter updating markers, regenerate manifests:\n\n```bash\nmake manifests\n```\n\n<aside class=\"warning\">\n<h1>Scope Changes Are Breaking</h1>\n\nChanging CRD scope from Namespaced to Cluster (or vice versa) is a **breaking change**:\n- Existing custom resources will become invalid\n- Users must migrate their resources manually\n- Consider creating a new CRD with a different version instead\n\nOnly change scope during initial development before any production usage.\n</aside>\n\n## RBAC for CRD Scope\n\n### Namespace-Scoped CRDs\n\nControllers watching namespace-scoped CRDs use namespace-scoped RBAC:\n\n```go\n//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch\n//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update\n```\n\nGenerated RBAC (cluster-scoped manager):\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups: [\"cache.example.com\"]\n  resources: [\"memcacheds\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n```\n\nGenerated RBAC (namespace-scoped manager):\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: manager-role\n  namespace: manager-namespace\nrules:\n- apiGroups: [\"cache.example.com\"]\n  resources: [\"memcacheds\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n```\n\n### Cluster-Scoped CRDs\n\nControllers watching cluster-scoped CRDs **must** use cluster-wide RBAC:\n\n```go\n//+kubebuilder:rbac:groups=infrastructure.example.com,resources=databases,verbs=get;list;watch;create;update;patch;delete\n//+kubebuilder:rbac:groups=infrastructure.example.com,resources=databases/status,verbs=get;update;patch\n//+kubebuilder:rbac:groups=infrastructure.example.com,resources=databases/finalizers,verbs=update\n```\n\nGenerated RBAC (always ClusterRole for cluster-scoped CRDs):\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups: [\"infrastructure.example.com\"]\n  resources: [\"databases\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n```\n\n<aside class=\"note\">\n<h1>Important</h1>\n\nEven if your manager is namespace-scoped (watches only one namespace), if it manages cluster-scoped CRDs, it still needs `ClusterRole` permissions for those resources.\n\nManager scope and CRD scope are independent:\n- **Manager scope**: Controlled by cache configuration (which namespaces to watch)\n- **CRD scope**: Controlled by the CRD's `scope` field (resource visibility)\n</aside>\n\n## Version Conversion and Webhooks\n\nFor namespace-scoped CRDs with multiple versions, conversion webhooks must account for namespace scope:\n\n```go\n//+kubebuilder:webhook:path=/convert,mutating=false,failurePolicy=fail,groups=cache.example.com,resources=memcacheds,verbs=create;update,versions=v1;v1beta1,name=cmemcached.kb.io,sideEffects=None,admissionReviewVersions=v1\n```\n\nThe webhook must handle conversion for resources in any namespace. See the [multi-version tutorial](https://book.kubebuilder.io/multiversion-tutorial/tutorial) for details.\n\n## Testing\n\n### Testing Namespace-Scoped CRDs\n\n```bash\n# Create resource in namespace\nkubectl apply -f config/samples/cache_v1alpha1_memcached.yaml -n test-namespace\n\n# Verify it exists in that namespace only\nkubectl get memcacheds -n test-namespace\nkubectl get memcacheds -n other-namespace  # Should not find it\n```\n\n### Testing Cluster-Scoped CRDs\n\n```bash\n# Create cluster-scoped resource (no namespace)\nkubectl apply -f config/samples/infrastructure_v1_database.yaml\n\n# Verify it's cluster-wide\nkubectl get databases  # No namespace needed\n```\n\n## See Also\n\n- [Manager Scope](./manager-scope.md) - Configuring manager watching scope\n- [Generating CRDs](./generating-crd.md) - CRD generation and markers\n- [Multi-Version Tutorial](https://book.kubebuilder.io/multiversion-tutorial/tutorial) - CRD versioning and conversion\n- [Kubernetes CRD Documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)\n"
  },
  {
    "path": "docs/book/src/reference/envtest.md",
    "content": "# Configuring envtest for integration tests\n\nThe [`controller-runtime/pkg/envtest`][envtest] Go library helps write integration tests for your controllers by setting up and starting an instance of etcd and the\nKubernetes API server, without kubelet, controller-manager or other components.\n\n## Installation\n\nInstalling the binaries is as a simple as running `make envtest`. `envtest` will download the Kubernetes API server binaries to the `bin/` folder in your project\nby default. `make test` is the one-stop shop for downloading the binaries, setting up the test environment, and running the tests.\n\n\nYou can refer to the Makefile of the Kubebuilder scaffold and observe that the envtest setup is consistently aligned across all controller-runtime releases.Starting from `release-0.19`, it is configured to automatically download the artefact from the correct location, **ensuring that kubebuilder users are not impacted.**\n\n```shell\n## Tool Binaries\n..\nENVTEST ?= $(LOCALBIN)/setup-envtest\n...\n\n## Tool Versions\n...\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell go list -m -f \"{{ .Version }}\" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf \"release-%d.%d\", $$2, $$3}')\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell go list -m -f \"{{ .Version }}\" k8s.io/api | awk -F'[v.]' '{printf \"1.%d\", $$3}')\n...\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n```\n\n## Installation in Air Gapped/disconnected environments\nIf you would like to download the tarball containing the binaries, to use in a disconnected environment you can use\n[`setup-envtest`][setup-envtest] to download the required binaries locally. There are a lot of ways to configure `setup-envtest` to avoid talking to\nthe internet you can read about them [here](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest#what-if-i-dont-want-to-talk-to-the-internet).\nThe examples below will show how to install the Kubernetes API binaries using mostly defaults set by `setup-envtest`.\n\n### Download the binaries\n`make envtest` will download the `setup-envtest` binary to `./bin/`.\n```shell\nmake envtest\n```\n\nInstalling the binaries using `setup-envtest` stores the binary in OS specific locations, you can read more about them\n[here](https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest#where-does-it-put-all-those-binaries)\n```sh\n./bin/setup-envtest use 1.31.0\n```\n\n### Update the test make target\nOnce these binaries are installed, change the `test` make target to include a `-i` like below. `-i` will only check for locally installed\nbinaries and not reach out to remote resources. You could also set the `ENVTEST_INSTALLED_ONLY` env variable.\n\n```makefile\ntest: manifests generate fmt vet\n    KUBEBUILDER_ASSETS=\"$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -i --bin-dir $(LOCALBIN) -p path)\" go test ./... -coverprofile cover.out\n```\n\nNOTE: The `ENVTEST_K8S_VERSION` needs to match the `setup-envtest` you downloaded above. Otherwise, you will see an error like the below\n```sh\nno such version (1.24.5) exists on disk for this architecture (darwin/amd64) -- try running `list -i` to see what's on disk\n```\n\n## Writing tests\n\nUsing `envtest` in integration tests follows the general flow of:\n\n```go\nimport sigs.k8s.io/controller-runtime/pkg/envtest\n\n//specify testEnv configuration\ntestEnv = &envtest.Environment{\n\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"config\", \"crd\", \"bases\")},\n}\n\n//start testEnv\ncfg, err = testEnv.Start()\n\n//write test logic\n\n//stop testEnv\nerr = testEnv.Stop()\n```\n\n`kubebuilder` does the boilerplate setup and teardown of testEnv for you, in the ginkgo test suite that it generates under the `/controllers` directory.\n\nLogs from the test runs are prefixed with `test-env`.\n\n<aside class=\"note\">\n<h1>Examples</h1>\n\nYou can use the plugin [DeployImage](../plugins/available/deploy-image-plugin-v1-alpha.md) to check examples. This plugin allows users to scaffold API/Controllers to deploy and manage an Operand (image) on the cluster following the guidelines and best practices. It abstracts the complexities of achieving this goal while allowing users to customize the generated code.\n\nTherefore, you can check that a test using ENV TEST will be generated for the controller which has the purpose to ensure that the Deployment is created successfully. You can see an example of its code implementation under the `testdata` directory with the [DeployImage](../plugins/available/deploy-image-plugin-v1-alpha.md) [samples here](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.6.0/testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go).\n\n</aside>\n\n### Configuring your test control plane\n\nController-runtime’s [envtest][envtest] framework requires `kubectl`, `kube-apiserver`, and `etcd` binaries be present locally to simulate the API portions of a real cluster.\n\nThe `make test` command will install these binaries to the `bin/` directory and use them when running tests that use `envtest`.\nIe,\n```shell\n./bin/k8s/\n└── 1.25.0-darwin-amd64\n    ├── etcd\n    ├── kube-apiserver\n    └── kubectl\n```\n\nYou can use environment variables and/or flags to specify the `kubectl`,`api-server` and `etcd` setup within your integration tests.\n\n### Environment Variables\n\n| Variable name | Type | When to use |\n| --- | :--- | :---                                                                                                                                                                                                                                                    |\n| `USE_EXISTING_CLUSTER` | boolean | Instead of setting up a local control plane, point to the control plane of an existing cluster. |\n| `KUBEBUILDER_ASSETS` | path to directory | Point integration tests to a directory containing all binaries (api-server, etcd and kubectl).                                                                                                                                                          |\n| `TEST_ASSET_KUBE_APISERVER`, `TEST_ASSET_ETCD`, `TEST_ASSET_KUBECTL` | paths to, respectively, api-server, etcd and kubectl binaries | Similar to `KUBEBUILDER_ASSETS`, but more granular. Point integration tests to use binaries other than the default ones. These environment variables can also be used to ensure specific tests run with expected versions of these binaries.            |\n| `KUBEBUILDER_CONTROLPLANE_START_TIMEOUT` and `KUBEBUILDER_CONTROLPLANE_STOP_TIMEOUT` | durations in format supported by [`time.ParseDuration`](https://golang.org/pkg/time/#ParseDuration) | Specify timeouts different from the default for the test control plane to (respectively) start and stop; any test run that exceeds them will fail.                                                                                                      |\n| `KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT` | boolean | Set to `true` to attach the control plane's stdout and stderr to os.Stdout and os.Stderr. This can be useful when debugging test failures, as output will include output from the control plane.                                                        |\n\nSee that the `test` makefile target will ensure that all is properly setup when you are using it. However, if you would like to run the tests without use the Makefile targets, for example via an IDE, then you can set the environment variables directly in the code of your `suite_test.go`:\n\n```go\nvar _ = BeforeSuite(func(done Done) {\n\tExpect(os.Setenv(\"TEST_ASSET_KUBE_APISERVER\", \"../bin/k8s/1.25.0-darwin-amd64/kube-apiserver\")).To(Succeed())\n\tExpect(os.Setenv(\"TEST_ASSET_ETCD\", \"../bin/k8s/1.25.0-darwin-amd64/etcd\")).To(Succeed())\n\tExpect(os.Setenv(\"TEST_ASSET_KUBECTL\", \"../bin/k8s/1.25.0-darwin-amd64/kubectl\")).To(Succeed())\n\t// OR\n\tExpect(os.Setenv(\"KUBEBUILDER_ASSETS\", \"../bin/k8s/1.25.0-darwin-amd64\")).To(Succeed())\n\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\ttestenv = &envtest.Environment{}\n\n\t_, err := testenv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tclose(done)\n}, 60)\n\nvar _ = AfterSuite(func() {\n\tExpect(testenv.Stop()).To(Succeed())\n\n\tExpect(os.Unsetenv(\"TEST_ASSET_KUBE_APISERVER\")).To(Succeed())\n\tExpect(os.Unsetenv(\"TEST_ASSET_ETCD\")).To(Succeed())\n\tExpect(os.Unsetenv(\"TEST_ASSET_KUBECTL\")).To(Succeed())\n\n})\n```\n\n<aside class=\"note\">\n<h1>ENV TEST Config Options</h1>\n\nYou can look at the controller-runtime docs to know more about its configuration options, see [here](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest#Environment). On top of that, if you are\nlooking to use ENV TEST to test your webhooks then you might want to give a look at its install [options](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest#WebhookInstallOptions).\n\n</aside>\n\n### Flags\nHere's an example of modifying the flags with which to start the API server in your integration tests, compared to the default values in `envtest.DefaultKubeAPIServerFlags`:\n\n```go\ncustomApiServerFlags := []string{\n\t\"--secure-port=6884\",\n\t\"--admission-control=MutatingAdmissionWebhook\",\n}\n\napiServerFlags := append([]string(nil), envtest.DefaultKubeAPIServerFlags...)\napiServerFlags = append(apiServerFlags, customApiServerFlags...)\n\ntestEnv = &envtest.Environment{\n\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"config\", \"crd\", \"bases\")},\n\tKubeAPIServerFlags: apiServerFlags,\n}\n```\n\n## Testing considerations\n\nUnless you're using an existing cluster, keep in mind that no built-in controllers are running in the test context. In some ways, the test control plane will behave differently from \"real\" clusters, and that might have an impact on how you write tests. One common example is garbage collection; because there are no controllers monitoring built-in resources, objects do not get deleted, even if an `OwnerReference` is set up.\n\nTo test that the deletion lifecycle works, test the ownership instead of asserting on existence. For example:\n\n```go\nexpectedOwnerReference := v1.OwnerReference{\n\tKind:       \"MyCoolCustomResource\",\n\tAPIVersion: \"my.api.example.com/v1beta1\",\n\tUID:        \"d9607e19-f88f-11e6-a518-42010a800195\",\n\tName:       \"userSpecifiedResourceName\",\n}\nExpect(deployment.ObjectMeta.OwnerReferences).To(ContainElement(expectedOwnerReference))\n```\n\n<aside class=\"warning\">\n\n<h2>Namespace usage limitation</h2>\n\nEnvTest does not support namespace deletion. Deleting a namespace will seem to succeed, but the namespace will just be put in a Terminating state, and never actually be reclaimed. Trying to recreate the namespace will fail. This will cause your reconciler to continue reconciling any objects left behind, unless they are deleted.\n\nTo overcome this limitation you can create a new namespace for each test. Even so, when one test completes (e.g. in \"namespace-1\") and another test starts (e.g. in \"namespace-2\"), the controller will still be reconciling any active objects from \"namespace-1\". This can be avoided by ensuring that all tests clean up after themselves as part of the test teardown.  If teardown of a namespace is difficult, it may be possible to wire the reconciler in such a way that it ignores reconcile requests that come from namespaces other than the one being tested:\n\n```go\ntype MyCoolReconciler struct {\n\tclient.Client\n\t...\n\tNamespace     string  // restrict namespaces to reconcile\n}\nfunc (r *MyCoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = r.Log.WithValues(\"myreconciler\", req.NamespacedName)\n\t// Ignore requests for other namespaces, if specified\n\tif r.Namespace != \"\" && req.Namespace != r.Namespace {\n\t\treturn ctrl.Result{}, nil\n\t}\n```\nWhenever your tests create a new namespace, it can modify the value of reconciler.Namespace. The reconciler will effectively ignore the previous namespace.\nFor further information see the issue raised in the controller-runtime [controller-runtime/issues/880](https://github.com/kubernetes-sigs/controller-runtime/issues/880) to add this support.\n</aside>\n\n## Cert-Manager and Prometheus options\n\nProjects scaffolded with Kubebuilder can enable the [`metrics`][metrics] and the [`cert-manager`][cert-manager] options. Note that when we are using the ENV TEST we are looking to test the controllers and their reconciliation. It is considered an integrated test because the ENV TEST API will do the test against a cluster and because of this the binaries are downloaded and used to configure its pre-requirements, however, its purpose is mainly to `unit` test the controllers.\n\nTherefore, to test a reconciliation in common cases you do not need to care about these options. However, if you would like to do tests with the Prometheus and the Cert-manager installed you can add the required steps to install them before running the tests.\nFollowing an example.\n\n```go\n    // Add the operations to install the Prometheus operator and the cert-manager\n    // before the tests.\n    BeforeEach(func() {\n        By(\"installing prometheus operator\")\n        Expect(utils.InstallPrometheusOperator()).To(Succeed())\n\n        By(\"installing the cert-manager\")\n        Expect(utils.InstallCertManager()).To(Succeed())\n    })\n\n    // You can also remove them after the tests::\n    AfterEach(func() {\n        By(\"uninstalling the Prometheus manager bundle\")\n        utils.UninstallPrometheusOperManager()\n\n        By(\"uninstalling the cert-manager bundle\")\n        utils.UninstallCertManager()\n    })\n```\n\nCheck the following example of how you can implement the above operations:\n\n```go\nconst (\n\tcertmanagerVersion = \"v1.5.3\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindCluster = \"kind\"\n\tdefaultKindBinary  = \"kind\"\n\n\tprometheusOperatorVersion = \"0.51\"\n\tprometheusOperatorURL     = \"https://raw.githubusercontent.com/prometheus-operator/\" + \"prometheus-operator/release-%s/bundle.yaml\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.\nfunc InstallPrometheusOperator() error {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// UninstallPrometheusOperator uninstalls the prometheus\nfunc UninstallPrometheusOperator() {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t//was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n```\nHowever, see that tests for the metrics and cert-manager might fit better well as e2e tests and not under the tests done using ENV TEST for the controllers. You might want to give a look at the [sample example][sdk-e2e-sample-example] implemented into [Operator-SDK][sdk] repository to know how you can write your e2e tests to ensure the basic workflows of your project.\nAlso, see that you can run the tests against a cluster where you have some configurations in place they can use the option to test using an existing cluster:\n\n```go\ntestEnv = &envtest.Environment{\n\tUseExistingCluster: true,\n}\n```\n\n<aside class=\"note\">\n<h1>Setup ENV TEST tool</h1>\nTo know more about the tooling used to configure ENVTEST which is used in the setup-envtest target in the Makefile\nof the projects build with Kubebuilder see the [README][readme]\nof its tooling.\n</aside>\n\n[metrics]: https://book.kubebuilder.io/reference/metrics.html\n[envtest]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/envtest\n[setup-envtest]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/tools/setup-envtest\n[cert-manager]: https://book.kubebuilder.io/cronjob-tutorial/cert-manager.html\n[sdk-e2e-sample-example]: https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4/memcached-operator/test/e2e\n[sdk]: https://github.com/operator-framework/operator-sdk\n[readme]: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md\n"
  },
  {
    "path": "docs/book/src/reference/generating-crd.md",
    "content": "# Generating CRDs\n\nKubebuilder uses a tool called [`controller-gen`][controller-tools] to\ngenerate utility code and Kubernetes object YAML, like\nCustomResourceDefinitions.\n\nTo do this, it makes use of special \"marker comments\" (comments that start\nwith `// +`) to indicate additional information about fields, types, and\npackages.  In the case of CRDs, these are generally pulled from your\n`_types.go` files.  For more information on markers, see the [marker\nreference docs][marker-ref].\n\nKubebuilder provides a `make` target to run controller-gen and generate\nCRDs: `make manifests`.\n\nWhen you run `make manifests`, you should see CRDs generated under the\n`config/crd/bases` directory.  `make manifests` can generate a number of\nother artifacts as well -- see the [marker reference docs][marker-ref] for\nmore details.\n\n## Validation\n\nCRDs support [declarative validation][kube-validation] using an [OpenAPI\nv3 schema][openapi-schema] in the `validation` section.\n\nIn general, [validation markers](./markers/crd-validation.md) may be\nattached to fields or to types. If you're defining complex validation, if\nyou need to re-use validation, or if you need to validate slice elements,\nit's often best to define a new type to describe your validation.\n\nFor example:\n\n```go\ntype ToySpec struct {\n\t// +kubebuilder:validation:MaxLength=15\n\t// +kubebuilder:validation:MinLength=1\n\tName string `json:\"name,omitempty\"`\n\n\t// +kubebuilder:validation:MaxItems=500\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:UniqueItems=true\n\tKnights []string `json:\"knights,omitempty\"`\n\n\tAlias   Alias   `json:\"alias,omitempty\"`\n\tRank    Rank    `json:\"rank\"`\n}\n\n// +kubebuilder:validation:Enum=Lion;Wolf;Dragon\ntype Alias string\n\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:Maximum=3\n// +kubebuilder:validation:ExclusiveMaximum=false\ntype Rank int32\n\n```\n\n## Additional Printer Columns\n\nStarting with Kubernetes 1.11, `kubectl get` can ask the server what\ncolumns to display.  For CRDs, this can be used to provide useful,\ntype-specific information with `kubectl get`, similar to the information\nprovided for built-in types.\n\nThe information that gets displayed can be controlled with the\n[additionalPrinterColumns field][kube-additional-printer-columns] on your\nCRD, which is controlled by the\n[`+kubebuilder:printcolumn`][crd-markers] marker on the Go type for\nyour CRD.\n\nFor instance, in the following example, we add fields to display\ninformation about the knights, rank, and alias fields from the validation\nexample:\n\n```go\n// +kubebuilder:printcolumn:name=\"Alias\",type=string,JSONPath=`.spec.alias`\n// +kubebuilder:printcolumn:name=\"Rank\",type=integer,JSONPath=`.spec.rank`\n// +kubebuilder:printcolumn:name=\"Bravely Run Away\",type=boolean,JSONPath=`.spec.knights[?(@ == \"Sir Robin\")]`,description=\"when danger rears its ugly head, he bravely turned his tail and fled\",priority=10\n// +kubebuilder:printcolumn:name=\"Age\",type=\"date\",JSONPath=\".metadata.creationTimestamp\"\ntype Toy struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ToySpec   `json:\"spec,omitempty\"`\n\tStatus ToyStatus `json:\"status,omitempty\"`\n}\n\n```\n\n## Subresources\n\nCRDs can choose to implement the `/status` and `/scale`\n[subresources][kube-subresources] as of Kubernetes 1.13.\n\nIt's generally recommended that you make use of the `/status` subresource\non all resources that have a status field.\n\nBoth subresources have a corresponding [marker][crd-markers].\n\n### Status\n\nThe status subresource is enabled via `+kubebuilder:subresource:status`.\nWhen enabled, updates at the main resource will not change status.\nSimilarly, updates to the status subresource cannot change anything but\nthe status field.\n\nFor example:\n\n```go\n// +kubebuilder:subresource:status\ntype Toy struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ToySpec   `json:\"spec,omitempty\"`\n\tStatus ToyStatus `json:\"status,omitempty\"`\n}\n```\n\n### Scale\n\nThe scale subresource is enabled via `+kubebuilder:subresource:scale`.\nWhen enabled, users will be able to use `kubectl scale` with your\nresource.  If the `selectorpath` argument pointed to the string form of\na label selector, the HorizontalPodAutoscaler will be able to autoscale\nyour resource.\n\nFor example:\n\n```go\ntype CustomSetSpec struct {\n\tReplicas *int32 `json:\"replicas\"`\n}\n\ntype CustomSetStatus struct {\n\tReplicas int32 `json:\"replicas\"`\n    Selector string `json:\"selector\"` // this must be the string form of the selector\n}\n\n\n// +kubebuilder:subresource:status\n// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector\ntype CustomSet struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   CustomSetSpec   `json:\"spec,omitempty\"`\n\tStatus CustomSetStatus `json:\"status,omitempty\"`\n}\n```\n\n## Multiple Versions\n\nAs of Kubernetes 1.13, you can have multiple versions of your Kind defined\nin your CRD, and use a webhook to convert between them.\n\nFor more details on this process, see the [multiversion\ntutorial](/multiversion-tutorial/tutorial.md).\n\nBy default, Kubebuilder disables generating different validation for\ndifferent versions of the Kind in your CRD, to be compatible with older\nKubernetes versions.\n\nYou'll need to enable this by switching the line in your makefile that\nsays `CRD_OPTIONS ?= \"crd:trivialVersions=true,preserveUnknownFields=false`\nto `CRD_OPTIONS ?= crd:preserveUnknownFields=false` if using v1beta CRDs,\nand `CRD_OPTIONS ?= crd` if using v1 (recommended).\n\nThen, you can use the `+kubebuilder:storageversion` [marker][crd-markers]\nto indicate the [GVK](/cronjob-tutorial/gvks.md \"Group-Version-Kind\") that\nshould be used to store data by the API server.\n\n## Under the hood\n\nKubebuilder scaffolds out make rules to run `controller-gen`.  The rules\nwill automatically install controller-gen if it's not on your path using\n`go install` with Go modules.\n\nYou can also run `controller-gen` directly, if you want to see what it's\ndoing.\n\nEach controller-gen \"generator\" is controlled by an option to\ncontroller-gen, using the same syntax as markers. controller-gen\nalso supports different output \"rules\" to control how and where output goes.\nNotice the `manifests` make rule (condensed slightly to only generate CRDs):\n\n```makefile\n# Generate manifests for CRDs\nmanifests: controller-gen\n\t$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n```\n\nIt uses the `output:crd:artifacts` output rule to indicate that\nCRD-related config (non-code) artifacts should end up in\n`config/crd/bases` instead of `config/crd`.\n\nTo see all the options including generators for `controller-gen`, run\n\n```shell\n$ controller-gen -h\n```\n\nor, for more details:\n\n```shell\n$ controller-gen -hhh\n```\n\n[marker-ref]: ./markers.md \"Markers for Config/Code Generation\"\n\n[kube-validation]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation \"Custom Resource Definitions: Validation\"\n\n[openapi-schema]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject \"OpenAPI v3\"\n\n[kube-additional-printer-columns]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#additional-printer-columns \"Custom Resource Definitions: Additional Printer Columns\"\n\n[kube-subresources]: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#status-subresource \"Custom Resource Definitions: Status Subresource\"\n\n[crd-markers]: ./markers/crd.md \"CRD Generation\"\n\n[controller-tools]: https://sigs.k8s.io/controller-tools \"Controller Tools\"\n"
  },
  {
    "path": "docs/book/src/reference/good-practices.md",
    "content": "# Good Practices\n\n## What is \"Reconciliation\" in Operators?\n\nWhen you create a project using Kubebuilder, see the scaffolded code generated under `cmd/main.go`. This code initializes a [Manager][controller-runtime-manager], and the project relies on the [controller-runtime][controller-runtime] framework. The Manager manages [Controllers][controllers], which offer a reconcile function that synchronizes resources until the desired state is achieved within the cluster.\n\nReconciliation is an ongoing loop that executes necessary operations to maintain the desired state, adhering to Kubernetes principles, such as the [control loop][k8s-control-loop]. For further information, check out the [Operator patterns][k8s-operator-pattern] documentation from Kubernetes to better understand those concepts.\n\n## Why should reconciliations be idempotent?\n\nWhen developing operators, the controller’s reconciliation loop needs to be idempotent. By following the [Operator pattern][operator-pattern] we create [controllers][controllers] that provide a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster. Developing idempotent solutions will allow the reconciler to correctly respond to generic or unexpected events, easily deal with application startup or upgrade. More explanation on this is available [here][controller-runtime-topic].\n\nWriting reconciliation logic according to specific events, breaks the recommendation of operator pattern and goes against the design principles of [controller-runtime][controller-runtime]. This may lead to unforeseen consequences, such as resources becoming stuck and requiring manual intervention.\n\n## Understanding Kubernetes APIs and following API conventions\n\nBuilding your operator commonly involves extending the Kubernetes API itself. It is helpful to understand precisely how Custom Resource Definitions (CRDs) interact with the Kubernetes API. Also, the [Kubebuilder documentation][docs] on Groups and Versions and Kinds may be helpful to understand these concepts better as they relate to operators.\n\nAdditionally, we recommend checking the documentation on [Operator patterns][operator-pattern] from Kubernetes to better understand the purpose of the standard solutions built with KubeBuilder.\n\n## Why you should adhere to the Kubernetes API conventions and standards\n\nEmbracing the [Kubernetes API conventions and standards][k8s-api-conventions] is crucial for maximizing the potential of your applications and deployments. By adhering to these established practices, you can benefit in several ways.\n\nFirstly, adherence ensures seamless interoperability within the Kubernetes ecosystem. Following conventions allows your applications to work harmoniously with other components, reducing compatibility issues and promoting a consistent user experience.\n\nSecondly, sticking to API standards enhances the maintainability and troubleshooting of your applications. Adopting familiar patterns and structures makes debugging and supporting your deployments easier, leading to more efficient operations and quicker issue resolution.\n\nFurthermore, leveraging the Kubernetes API conventions empowers you to harness the platform's full capabilities. By working within the defined framework, you can leverage the rich set of features and resources offered by Kubernetes, enabling scalability, performance optimization, and resilience.\n\nLastly, embracing these standards future-proofs your native solutions. By aligning with the evolving Kubernetes ecosystem, you ensure compatibility with future updates, new features, and enhancements introduced by the vibrant Kubernetes community.\n\nIn summary, by adhering to the Kubernetes API conventions and standards, you unlock the potential for seamless integration, simplified maintenance, optimal performance, and future-readiness, all contributing to the success of your applications and deployments.\n\n## Why should one avoid a system design where a single controller is responsible for managing multiple CRDs (Custom Resource Definitions)(for example, an _'install_all_controller.go'_)?\n\nAvoid a design solution where the same controller reconciles more than one Kind. Having many Kinds (such as CRDs), that are all managed by the same controller, usually goes against the design proposed by controller-runtime. Furthermore, this might hurt concepts such as encapsulation, the Single Responsibility Principle, and Cohesion. Damaging these concepts may cause unexpected side effects and increase the difficulty of extending, reusing, or maintaining the operator.\nHaving one controller manage many Custom Resources (CRs) in an Operator can lead to several issues:\n\n- **Complexity**: A single controller managing multiple CRs can increase the complexity of the code, making it harder to understand, maintain, and debug.\n- **Scalability**: Each controller typically manages a single kind of CR for scalability. If a single controller handles multiple CRs, it could become a bottleneck, reducing the overall efficiency and responsiveness of your system.\n- **Single Responsibility Principle**: Following this principle from software engineering, each controller should ideally have only one job. This approach simplifies development and debugging, and makes the system more robust.\n- **Error Isolation**: If one controller manages multiple CRs and an error occurs, it could potentially impact all the CRs it manages. Having a single controller per CR ensures that an issue with one controller or CR does not directly affect others.\n- **Concurrency and Synchronization**: A single controller managing multiple CRs could lead to race conditions and require complex synchronization, especially if the CRs have interdependencies.\n\nIn conclusion, while it might seem efficient to have a single controller manage multiple CRs, it often leads to higher complexity, lower scalability, and potential stability issues. It's generally better to adhere to the single responsibility principle, where each CR is managed by its own controller.\n\n## Why You Should Adopt Status Conditions\n\nWe recommend you manage your solutions using Status Conditionals following the [K8s Api conventions][k8s-api-conventions] because:\n\n- **Standardization**: Conditions provide a standardized way to represent the state of an Operator's custom resources, making it easier for users and tools to understand and interpret the resource's status.\n- **Readability**: Conditions can clearly express complex states by using a combination of multiple conditions, making it easier for users to understand the current state and progress of the resource.\n- **Extensibility**: As new features or states are added to your Operator, conditions can be easily extended to represent these new states without requiring significant changes to the existing API or structure.\n- **Observability**: Status conditions can be monitored and tracked by cluster administrators and external monitoring tools, enabling better visibility into the state of the custom resources managed by the Operator.\n- **Compatibility**: By adopting the common pattern of using conditions in Kubernetes APIs, Operator authors ensure their custom resources align with the broader ecosystem, which helps users to have a consistent experience when interacting with multiple Operators and resources in their clusters.\n\n<aside class=\"note\">\n<h1> Example of Usage </h1>\n\nCheck out the [Deploy Image Plugin][deploy-image]. This plugin allows users to scaffold API/Controllers to deploy and manage an Operand (image) on the cluster following the guidelines and best practices. It abstracts the\ncomplexities of achieving this goal while allowing users to customize the generated code.\n\nTherefore, you can check an example of Status Conditional usage by looking at its API(s) scaffolded and code implemented under the Reconciliation into its Controllers.\n\n</aside>\n\n## You Should Adopt K8s Conventions for Instrumentation and Observability\n\nProper logging is essential for observability in Kubernetes-native applications. However, it's important to understand which logging conventions to apply based on the context of your code.\n\n### Understanding Go vs. Kubernetes Logging Conventions\n\nWhen developing with Go, you may be familiar with the [Go Code Review Comments][go-code-review] guidelines, which state that error strings should not be capitalized and should not end with punctuation. These conventions are designed for error messages that are often composed into larger contexts:\n\n```go\n// Go conventions (for general Go code, libraries, CLI tools)\nreturn fmt.Errorf(\"something bad happened\")  // lowercase, no period\nlog.Printf(\"failed to connect: %v\", err)     // lowercase\n```\n\n**However**, when developing Kubernetes-native solutions (controllers, operators, webhooks) that run on the cluster, you should follow the [Kubernetes Logging Conventions][k8s-logging] for better observability and consistency with the Kubernetes ecosystem.\n\n### Kubernetes Logging Conventions\n\nFor controllers, operators, and webhooks, follow these guidelines:\n\n- Start from a capital letter.\n- Do not end the message with a period.\n- Use active voice. Use complete sentences when there is an acting subject (\"A could not do B\") or omit the subject if the subject would be the program itself (\"Could not do B\").\n- Use past tense (\"Could not delete B\" instead of \"Cannot delete B\")\n- When referring to an object, state what type of object it is. (\"Deleted Pod\" instead of \"Deleted\")\n- Use structured logging with balanced key-value pairs.\n\n**Examples:**\n\n```go\n// Kubernetes conventions (for controllers, operators, webhooks)\nlog.Info(\"Starting reconciliation\")                              // Capital letter, no period\nlog.Info(\"Creating Deployment\", \"name\", name, \"namespace\", ns)   // Specify object type, structured logging\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)              // Past tense, specify type\nlog.Error(err, \"Failed to create Pod\", \"name\", name)             // Past tense, specify type\nlog.Info(\"Deployment could not create Pod\", \"deployment\", name)  // Acting subject\nlog.Info(\"Could not delete Pod\", \"name\", name)                   // Subject is the program itself\n```\n\n### Why Different Conventions?\n\n- **Go conventions** are optimized for error messages that get composed into larger contexts and displayed inline with other text\n- **Kubernetes conventions** are optimized for structured logging in distributed systems where logs are:\n  - Aggregated from multiple components across the cluster\n  - Parsed by log collectors (Fluentd, Fluentbit, Loki, etc.)\n  - Displayed in monitoring dashboards and UIs\n  - Used for alerting and troubleshooting in production\n\nFollowing these conventions ensures your logs integrate seamlessly with Kubernetes observability tools and provide clear, actionable information for cluster operators and SREs.\n\n[docs]: /cronjob-tutorial/gvks.html\n[operator-pattern]: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n[controllers]: https://kubernetes.io/docs/concepts/architecture/controller/\n[controller-runtime-topic]: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md#q-how-do-i-have-different-logic-in-my-reconciler-for-different-types-of-events-eg-create-update-delete\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[deploy-image]: /plugins/available/deploy-image-plugin-v1-alpha.md\n[controller-runtime-manager]: https://github.com/kubernetes-sigs/controller-runtime/blob/304027bcbe4b3f6d582180aec5759eb4db3f17fd/pkg/manager/manager.go#L53\n[k8s-api-conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n[k8s-control-loop]: https://kubernetes.io/docs/concepts/architecture/controller/\n[k8s-operator-pattern]: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n[go-code-review]: https://go.dev/wiki/CodeReviewComments#error-strings\n[k8s-logging]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n"
  },
  {
    "path": "docs/book/src/reference/kind-config.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n  - role: worker\n  - role: worker\n  - role: worker\n"
  },
  {
    "path": "docs/book/src/reference/kind.md",
    "content": "# Using Kind For Development Purposes and CI\n\n## Why Use Kind\n\n- **Fast Setup:** Launch a multi-node Kubernetes cluster locally in under a minute.\n- **Quick Teardown:** Dismantle the cluster in just a few seconds, streamlining your development workflow.\n- **Local Image Usage:** Deploy your container images directly without the need to push to a remote registry.\n- **Lightweight and Efficient:** Kind is a minimalistic Kubernetes distribution, making it perfect for local development and CI/CD pipelines.\n\nThis only cover the basics to use a kind cluster. You can find more details at\n[kind documentation](https://kind.sigs.k8s.io/).\n\n## Installation\n\nYou can follow [this](https://kind.sigs.k8s.io/#installation-and-usage) to\ninstall `kind`.\n\n## Create a Cluster\n\nYou can simply create a `kind` cluster by\n\n```bash\nkind create cluster\n```\n\nTo customize your cluster, you can provide additional configuration.\nFor example, the following is a sample `kind` configuration.\n\n```yaml\n{{#include ./kind-config.yaml}}\n```\n\nUsing the configuration above, run the following command will give you a k8s\nv1.17.2 cluster with 1 control-plane node and 3 worker nodes.\n\n```bash\nkind create cluster --config hack/kind-config.yaml --image=kindest/node:v1.17.2\n```\n\nYou can use `--image` flag to specify the cluster version you want, e.g.\n`--image=kindest/node:v1.17.2`, the supported version are listed\n[here](https://hub.docker.com/r/kindest/node/tags).\n\n## Load Docker Image into the Cluster\n\nWhen developing with a local kind cluster, loading docker images to the cluster\nis a very useful feature. You can avoid using a container registry.\n\n```bash\nkind load docker-image your-image-name:your-tag\n```\n\nSee [Load a local image into a kind cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster) for more information.\n\n## Delete a Cluster\n\n```bash\nkind delete cluster\n```\n"
  },
  {
    "path": "docs/book/src/reference/manager-scope.md",
    "content": "# Manager Scope\n\nManager scope determines which namespace(s) your manager watches and manages resources in.\n\n<aside class=\"note\">\n<h1>Manager Scope vs CRD Scope</h1>\n\nManager scope is independent from CRD scope. See [Understanding Scopes](./scopes.md) for an explanation of how these two concepts differ.\n</aside>\n\n## Overview\n\nKubebuilder supports three types of manager scope:\n\n| Scope | Description | Use Case |\n|-------|-------------|----------|\n| **Cluster-scoped (default)** | Watches all namespaces in the cluster | Single manager managing resources cluster-wide |\n| **Namespace-scoped** | Watches only specific namespace(s) | Multi-tenant, least-privilege deployments |\n| **Multi-namespace** | Watches multiple specific namespaces | Manager managing resources in subset of namespaces |\n\nManager scope is configured through:\n- RBAC resources (Role vs ClusterRole)\n- Cache configuration in `cmd/main.go`\n- `WATCH_NAMESPACE` environment variable\n\n## Cluster-Scoped (Default)\n\nBy default, Kubebuilder scaffolds cluster-scoped managers that watch all namespaces in the cluster.\n\n```bash\nkubebuilder init --domain example.com\n```\n\n**Characteristics:**\n- Uses `ClusterRole` and `ClusterRoleBinding` for RBAC\n- Manager watches all namespaces\n- No cache configuration needed\n\n**When to use:**\n- Single manager instance for the entire cluster\n- Managing cluster-scoped resources (Nodes, ClusterRoles, Namespaces)\n- Simpler RBAC model when cluster-wide access is acceptable\n\n## Namespace-Scoped\n\nNamespace-scoped managers watch only specific namespace(s), configured via the `WATCH_NAMESPACE` environment variable.\n\n```bash\n# New projects\nkubebuilder init --domain example.com --namespaced\n\n# Existing projects\nkubebuilder edit --namespaced=true\n```\n\n**Characteristics:**\n- Uses namespace-scoped `Role` and `RoleBinding` for RBAC\n- Manager watches only specified namespace(s)\n- Requires cache configuration in `cmd/main.go`\n- Requires `namespace=` parameter in controller RBAC markers\n\n**When to use:**\n- Multi-tenant environments (one manager per tenant/namespace)\n- Security policies requiring least-privilege access\n- Multiple manager instances in different namespaces\n\n**RBAC markers:**\n\nControllers in namespace-scoped projects use the `namespace=` parameter in RBAC markers to generate namespace-scoped `Role` resources:\n\n```go\n// +kubebuilder:rbac:groups=myapp.example.com,namespace=myproject-system,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=myapp.example.com,namespace=myproject-system,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=myapp.example.com,namespace=myproject-system,resources=mykinds/finalizers,verbs=update\n```\n\nWhen controller-gen sees the `namespace=` parameter, it generates `kind: Role` instead of `kind: ClusterRole`. The namespace field is added by kustomize during the build process (configured in `config/default/kustomization.yaml`).\n\n**Cache configuration:**\n\nKubebuilder automatically scaffolds the cache configuration in `cmd/main.go` when using `--namespaced` flag:\n\n```go\n// setupCacheNamespaces configures the cache to watch specific namespace(s).\n// It supports both single namespace (\"ns1\") and multi-namespace (\"ns1,ns2,ns3\") formats.\nfunc setupCacheNamespaces(namespaces string) cache.Options {\n    defaultNamespaces := make(map[string]cache.Config)\n    for ns := range strings.SplitSeq(namespaces, \",\") {\n        defaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}\n    }\n    return cache.Options{\n        DefaultNamespaces: defaultNamespaces,\n    }\n}\n\n// In main()\nwatchNamespace, err := getWatchNamespace()\nif err != nil {\n    setupLog.Error(err, \"Unable to get WATCH_NAMESPACE\")\n    os.Exit(1)\n}\n\nmgrOptions := ctrl.Options{\n    Scheme:                 scheme,\n    Metrics:                metricsServerOptions,\n    WebhookServer:          webhookServer,\n    HealthProbeBindAddress: probeAddr,\n    LeaderElection:         enableLeaderElection,\n    LeaderElectionID:       \"your-leader-election-id\",\n}\n\n// Configure cache to watch namespace(s) specified in WATCH_NAMESPACE\nmgrOptions.Cache = setupCacheNamespaces(watchNamespace)\nsetupLog.Info(\"Watching namespace(s)\", \"namespaces\", watchNamespace)\n\nmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)\n```\n\nThis configuration works for both single namespace (`WATCH_NAMESPACE=my-namespace`) and multi-namespace (`WATCH_NAMESPACE=ns1,ns2,ns3`) scenarios.\n\n## Multi-Namespace\n\nManagers can watch multiple specific namespaces using comma-separated values in `WATCH_NAMESPACE`.\n\n**Characteristics:**\n- Requires `Role` and `RoleBinding` in each watched namespace\n- Uses the same `setupCacheNamespaces` helper function\n- Same code as single-namespace mode (KISS principle)\n\n**Example:**\n\n```bash\n# Deploy manager to watch multiple namespaces\nexport WATCH_NAMESPACE=namespace1,namespace2,namespace3\nkubectl apply -f dist/install.yaml\n```\n\nThe `setupCacheNamespaces` helper function automatically handles both single and multiple namespaces without conditional logic.\n\n<aside class=\"note\">\n<h1>Example</h1>\n\nThe `testdata/project-v4-with-plugins` in the Kubebuilder repository demonstrates a complete namespace-scoped manager configuration.\n\nSee: [testdata/project-v4-with-plugins](https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins)\n</aside>\n\n<aside class=\"warning\">\n\n<h1>Webhooks and Namespace-Scoped Mode</h1>\n\nIf your project has webhooks, the manager cache is restricted to `WATCH_NAMESPACE`, but webhooks receive requests from all namespaces by default.\n\n**The Problem:**\n\nYour webhook server receives admission requests from all namespaces, but the cache only has data from `WATCH_NAMESPACE`. If a webhook handler queries the cache for an object outside the watched namespaces, the lookup fails.\n\n**Solution:**\n\nConfigure `namespaceSelector` or `objectSelector` on your webhooks to align webhook scope with the cache. Currently, controller-gen does not have markers for this. You must add these manually using Kustomize patches.\n\nSee the [Webhook Bootstrap Problem](../reference/webhook-bootstrap-problem.html) guide for detailed steps on creating and applying namespace selector patches.\n\n</aside>\n\n## See Also\n\n- [Understanding Scopes](./scopes.md) - Overview of manager and CRD scopes\n- [CRD Scope](./crd-scope.md) - Configuring CustomResourceDefinition scope\n- [Namespace-Scoped Migration](../migration/namespace-scoped.md) - Detailed implementation guide\n- [Project Config](./project-config.md) - PROJECT file configuration\n"
  },
  {
    "path": "docs/book/src/reference/markers/crd-processing.md",
    "content": "# CRD Processing\n\nThese markers help control how the Kubernetes API server processes API\nrequests involving your custom resources.\n\nSee [Generating CRDs](/reference/generating-crd.md) for examples.\n\n{{#markerdocs CRD processing}}\n"
  },
  {
    "path": "docs/book/src/reference/markers/crd-validation.md",
    "content": "# CRD Validation\n\nThese markers modify how the CRD validation schema is produced for the\ntypes and fields they modify.  Each corresponds roughly to an OpenAPI/JSON\nschema option.\n\nSee [Generating CRDs](/reference/generating-crd.md) for examples.\n\n<aside class=\"note\">\n<h1>Understanding Marker Grouping in Documentation</h1>\n\nCertain markers may seem duplicated. However, these markers are grouped based on their context of use\n— such as fields, types, or arrays. For instance, a marker like `+kubebuilder:validation:Enum` can be applied to\nindividual fields or array items, and this flexibility is reflected in the documentation.\n\nThe grouping ensures clarity by showing how the same marker can be reused for different purposes.\n\n</aside>\n\n<aside class=\"note\">\n<h1>Schema & Validation</h1>\n\nCustom resources are validated using the generated OpenAPI v3 schema and must comply with Kubernetes structural schema rules.\nOnly field types and constraints that can be represented in the CRD schema are enforced by the API server.\n\nNumeric types are constrained by [Kubernetes CRD OpenAPI v3 schema compatibility](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation).\nIn practice, prefer Go types that map cleanly to the supported OpenAPI formats (for example, `int32` and `int64` for integers).\nFor values that require decimal-like representation, use `resource.Quantity`.\n\nUse Kubebuilder validation markers to declare additional constraints — such as minimum and maximum values, length limits, patterns, enums, and list or map rules — so they are reflected in the generated CRD and validated at runtime.\n</aside>\n\n{{#markerdocs CRD validation}}\n"
  },
  {
    "path": "docs/book/src/reference/markers/crd.md",
    "content": "# CRD Generation\n\nThese markers describe how to construct a custom resource definition from\na series of Go types and packages.  Generation of the actual validation\nschema is described by the [validation markers](./crd-validation.md).\n\nSee [Generating CRDs](../generating-crd.md) for examples.\n\n{{#markerdocs CRD}}\n"
  },
  {
    "path": "docs/book/src/reference/markers/object.md",
    "content": "# Object/DeepCopy\n\nThese markers control when `DeepCopy` and `runtime.Object` implementation\nmethods are generated.\n\n{{#markerdocs object}}\n"
  },
  {
    "path": "docs/book/src/reference/markers/rbac.md",
    "content": "# RBAC\n\nThese markers cause an [RBAC\nClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole)\nto be generated.  This allows you to describe the permissions that your\ncontroller requires alongside the code that makes use of those\npermissions.\n\n{{#markerdocs RBAC}}\n"
  },
  {
    "path": "docs/book/src/reference/markers/scaffold.md",
    "content": "# Scaffold\n\nThe `+kubebuilder:scaffold` marker is a key part of the Kubebuilder scaffolding system. It marks locations in generated\nfiles where additional code will be injected as new resources (such as controllers, webhooks, or APIs) are scaffolded.\nThis enables Kubebuilder to seamlessly integrate newly generated components into the project without affecting\nuser-defined code.\n\n<aside class=\"warning\">\n    <h3>If you delete or change the `+kubebuilder:scaffold` markers</h3>\n\nThe Kubebuilder CLI specifically looks for these markers in expected\nfiles during code generation. If the marker is moved or removed, the CLI will\nnot be able to inject the necessary code, and the scaffolding process may\nfail or behave unexpectedly.\n\n</aside>\n\n## How It Works\n\nWhen you scaffold a new resource using the Kubebuilder CLI (e.g., `kubebuilder create api`),\nthe CLI identifies `+kubebuilder:scaffold` markers in key locations and uses them as placeholders\nto insert the required imports and registration code.\n\n## Example Usage in `main.go`\n\nHere is how the `+kubebuilder:scaffold` marker is used in a typical `main.go` file. To illustrate how it works, consider the following command to create a new API:\n\n```shell\nkubebuilder create api --group crew --version v1 --kind Admiral --controller=true --resource=true\n```\n\n### To Add New Imports\n\nThe `+kubebuilder:scaffold:imports` marker allows the Kubebuilder CLI to inject additional imports,\nsuch as for new controllers or webhooks. When we create a new API, the CLI automatically adds the required import paths\nin this section.\n\nFor example, after creating the `Admiral` API in a single-group layout,\nthe CLI will add `crewv1 \"<repo-path>/api/v1\"` to the imports:\n\n```go\nimport (\n    \"crypto/tls\"\n    \"flag\"\n    \"os\"\n\n    // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n    // to ensure that exec-entrypoint and run can make use of them.\n    _ \"k8s.io/client-go/plugin/pkg/client/auth\"\n    ...\n    crewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n    // +kubebuilder:scaffold:imports\n)\n```\n\n### To Register a New Scheme\n\nThe `+kubebuilder:scaffold:scheme` marker is used to register newly created API versions with the runtime scheme,\nensuring the API types are recognized by the manager.\n\nFor example, after creating the Admiral API, the CLI will inject the\nfollowing code into the `init()` function to register the scheme:\n\n\n```go\nfunc init() {\n    ...\n    utilruntime.Must(crewv1.AddToScheme(scheme))\n    // +kubebuilder:scaffold:scheme\n}\n```\n\n## To Set Up a Controller\n\nWhen we create a new controller (e.g., for Admiral), the Kubebuilder CLI injects the controller\nsetup code into the manager using the `+kubebuilder:scaffold:builder` marker. This marker indicates where\nthe setup code for new controllers should be added.\n\nFor example, after creating the `AdmiralReconciler`, the CLI will add the following code\nto register the controller with the manager:\n\n```go\nif err = (&crewv1.AdmiralReconciler{\n    Client: mgr.GetClient(),\n    Scheme: mgr.GetScheme(),\n}).SetupWithManager(mgr); err != nil {\n    setupLog.Error(err, \"unable to create controller\", \"controller\", \"Admiral\")\n    os.Exit(1)\n}\n// +kubebuilder:scaffold:builder\n```\n\nThe `+kubebuilder:scaffold:builder` marker ensures that newly scaffolded controllers are\nproperly registered with the manager, so that the controller can reconcile the resource.\n\n## List of `+kubebuilder:scaffold` Markers\n\n| Marker                                                                         | Usual Location               | Function                                                                        |\n|--------------------------------------------------------------------------------|------------------------------|---------------------------------------------------------------------------------|\n| `+kubebuilder:scaffold:imports`                                                | `main.go`                    | Marks where imports for new controllers, webhooks, or APIs should be injected.   |\n| `+kubebuilder:scaffold:scheme`                                                 | `init()` in `main.go`        | Used to add API versions to the scheme for runtime.                             |\n| `+kubebuilder:scaffold:builder`                                                | `main.go`                    | Marks where new controllers should be registered with the manager.              |\n| `+kubebuilder:scaffold:webhook`                                                | `webhooks suite tests` files | Marks where webhook setup functions are added.                                  |\n| `+kubebuilder:scaffold:crdkustomizeresource`                                   | `config/crd`                 | Marks where CRD custom resource patches are added.                              |\n| `+kubebuilder:scaffold:crdkustomizewebhookpatch`                               | `config/crd`                 | Marks where CRD webhook patches are added.                                      |\n| `+kubebuilder:scaffold:crdkustomizecainjectionns`                              | `config/default`             | Marks where CA injection patches are added for the conversion webhooks.                                                                                                                |\n| `+kubebuilder:scaffold:crdkustomizecainjectioname`                             | `config/default`             | Marks where CA injection patches are added for the conversion webhooks.                                                                                                                |\n| **(No longer supported)** `+kubebuilder:scaffold:crdkustomizecainjectionpatch` | `config/crd`                 | Marks where CA injection patches are added for the webhooks. Replaced by `+kubebuilder:scaffold:crdkustomizecainjectionns` and `+kubebuilder:scaffold:crdkustomizecainjectioname`  |\n| `+kubebuilder:scaffold:manifestskustomizesamples`                              | `config/samples`             | Marks where Kustomize sample manifests are injected.                            |\n| `+kubebuilder:scaffold:e2e-webhooks-checks`                                    | `test/e2e`                   | Adds e2e checks for webhooks depending on the types of webhooks scaffolded.      |\n| `+kubebuilder:scaffold:e2e-metrics-webhooks-readiness`                         | `test/e2e`                   | Adds readiness logic so metrics e2e tests wait for webhook service endpoints before creating pods. |\n<aside class=\"warning\">\n    <h3> **(No longer supported)** `+kubebuilder:scaffold:crdkustomizecainjectionpatch` </h3>\n\nIf you find this marker in your code please:\n\n1. **Remove the CERTMANAGER Section from `config/crd/kustomization.yaml`:**\n\n   Delete the `CERTMANAGER` section to prevent unintended CA injection patches for CRDs. Ensure the following lines are removed or commented out:\n\n   ```yaml\n   # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.\n   # patches here are for enabling the CA injection for each CRD\n   #- path: patches/cainjection_in_firstmates.yaml\n   # +kubebuilder:scaffold:crdkustomizecainjectionpatch\n   ```\n\n2. **Ensure CA Injection Configuration in `config/default/kustomization.yaml`:**\n\n   Under the `[CERTMANAGER]` replacement in `config/default/kustomization.yaml`, add the following code for proper CA injection generation:\n\n   **NOTE:** You must ensure that the code contains the following target markers:\n    - `+kubebuilder:scaffold:crdkustomizecainjectionns`\n    - `+kubebuilder:scaffold:crdkustomizecainjectioname`\n\n   ```yaml\n   # - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n   #     kind: Certificate\n   #     group: cert-manager.io\n   #     version: v1\n   #     name: serving-cert # This name should match the one in certificate.yaml\n   #     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   #   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n   # +kubebuilder:scaffold:crdkustomizecainjectionns\n   # - source:\n   #     kind: Certificate\n   #     group: cert-manager.io\n   #     version: v1\n   #     name: serving-cert # This name should match the one in certificate.yaml\n   #     fieldPath: .metadata.name\n   #   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n   # +kubebuilder:scaffold:crdkustomizecainjectioname\n   ```\n\n3. **Ensure Only Conversion Webhook Patches in `config/crd/patches`:**\n\n   The `config/crd/patches` directory and the corresponding entries in `config/crd/kustomization.yaml` should only contain files for conversion webhooks. Previously, a bug caused the patch file to be generated for any webhook, but only patches for webhooks scaffolded with the `--conversion` option should be included.\n\nFor further guidance, you can refer to examples in the `testdata/` directory in the Kubebuilder repository.\n\n> **Alternatively**: You can use the [`alpha generate`](./../rescaffold.md) command to re-generate the project from scratch\n> using the latest release available. Afterward, you can re-add only your code implementation on top to ensure your project\n> includes all the latest bug fixes and enhancements.\n\n</aside>\n\n<aside class=\"note\">\n<h1>Creating Your Own Markers</h1>\n\nIf you are using Kubebuilder as a library to create [your own plugins](./../../plugins/creating-plugins.md) and extend its CLI functionalities,\nyou have the flexibility to define and use your own markers. To implement your own markers, refer to the [kubebuilder/v4/pkg/machinery](https://pkg.go.dev/sigs.k8s.io/kubebuilder/v4/pkg/machinery),\nwhich provides tools to create and manage markers effectively.\n\n</aside>\n\n\n\n"
  },
  {
    "path": "docs/book/src/reference/markers/webhook.md",
    "content": "# Webhook\n\nThese markers describe how [webhook configuration](../webhook-overview.md) is generated.\nUse these to keep the description of your webhooks close to the code that\nimplements them.\n\n{{#markerdocs Webhook}}\n"
  },
  {
    "path": "docs/book/src/reference/markers.md",
    "content": "# Markers for Config/Code Generation\n\nKubebuilder makes use of a tool called\n[controller-gen](/reference/controller-gen.md) for\ngenerating utility code and Kubernetes YAML.  This code and config\ngeneration is controlled by the presence of special \"marker comments\" in\nGo code.\n\nMarkers are single-line comments that start with a plus, followed by\na marker name, optionally followed by some marker specific configuration:\n\n```go\n// +kubebuilder:validation:Optional\n// +kubebuilder:validation:MaxItems=2\n// +kubebuilder:printcolumn:JSONPath=\".status.replicas\",name=Replicas,type=string\n```\n\n<aside class=\"note\">\n<h1>difference between <code>// +optional</code> and <code>// +kubebuilder:validation:Optional</code></h1>\n\nController-gen supports both (see the output of `controller-gen crd -www`). `+kubebuilder:validation:Optional` and `+optional` can be applied to fields.\n\nBut `+kubebuilder:validation:Optional` can also be applied at the package level such that it applies to every field in the package.\n\nIf you're using controller-gen only then they're redundant, but if you're using other generators or you want developers that need to  build their own clients for your API, you'll want to also include `+optional`.\n\nThe most reliable way in 1.x to get `+optional` is `omitempty`.\n\n</aside>\n\nSee each subsection for information about different types of code and YAML\ngeneration.\n\n## Generating Code & Artifacts in Kubebuilder\n\nKubebuilder projects have two `make` targets that make use of\ncontroller-gen:\n\n- `make manifests` generates Kubernetes object YAML, like\n  [CustomResourceDefinitions](./markers/crd.md),\n  [WebhookConfigurations](./markers/webhook.md), and [RBAC\n  roles](./markers/rbac.md).\n\n- `make generate` generates code, like [runtime.Object/DeepCopy\n  implementations](./markers/object.md).\n\nSee [Generating CRDs](./generating-crd.md) for a comprehensive overview.\n\n## Marker Syntax\n\nExact syntax is described in the [godocs for\ncontroller-tools](https://pkg.go.dev/sigs.k8s.io/controller-tools/pkg/markers?tab=doc).\n\nIn general, markers may either be:\n\n- **Empty** (`+kubebuilder:validation:Optional`): empty markers are like boolean flags on the command line\n  -- just specifying them enables some behavior.\n\n- **Anonymous** (`+kubebuilder:validation:MaxItems=2`): anonymous markers take\n  a single value as their argument.\n\n- **Multi-option**\n  (`+kubebuilder:printcolumn:JSONPath=\".status.replicas\",name=Replicas,type=string`): multi-option\n  markers take one or more named arguments.  The first argument is\n  separated from the name by a colon, and latter arguments are\n  comma-separated.  Order of arguments doesn't matter.  Some arguments may\n  be optional.\n\nMarker arguments may be strings, ints, bools, slices, or maps thereof.\nStrings, ints, and bools follow their Go syntax:\n\n```go\n// +kubebuilder:validation:ExclusiveMaximum=false\n// +kubebuilder:validation:Format=\"date-time\"\n// +kubebuilder:validation:Maximum=42\n```\n\nFor convenience, in simple cases the quotes may be omitted from strings,\nalthough this is not encouraged for anything other than single-word\nstrings:\n\n```go\n// +kubebuilder:validation:Type=string\n```\n\nSlices may be specified either by surrounding them with curly braces and\nseparating with commas:\n\n```go\n// +kubebuilder:webhooks:Enum={\"crackers, Gromit, we forgot the crackers!\",\"not even wensleydale?\"}\n```\n\nor, in simple cases, by separating with semicolons:\n\n```go\n// +kubebuilder:validation:Enum=Wallace;Gromit;Chicken\n```\n\nMaps are specified with string keys and values of any type (effectively\n`map[string]interface{}`). A map is surrounded by curly braces (`{}`),\neach key and value is separated by a colon (`:`), and each key-value\npair is separated by a comma:\n\n```go\n// +kubebuilder:default={magic: {numero: 42, stringified: forty-two}}\n```\n"
  },
  {
    "path": "docs/book/src/reference/metrics-reference.md",
    "content": "# Default Exported Metrics References\n\nFollowing the metrics which are exported and provided by [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) by default:\n\n| Metrics name                                                                                                                                                             | Type      | Description                                                                                                                                                                                                                          |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [workqueue_depth](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L41)                                                       | Gauge     | Current depth of workqueue.                                                                                                                                                                                                          |\n| [workqueue_adds_total](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L47)                                                  | Counter   | Total number of adds handled by workqueue.                                                                                                                                                                                           |\n| [workqueue_queue_duration_seconds](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L53)                                      | Histogram | How long in seconds an item stays in workqueue before being requested.                                                                                                                                                               |\n| [workqueue_work_duration_seconds](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L60)                                       | Histogram | How long in seconds processing an item from workqueue takes.                                                                                                                                                                         |\n| [workqueue_unfinished_work_seconds](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L67)                                     | Gauge     | How many seconds of work has been done that is in progress and hasn't been observed by work_duration. Large values indicate stuck threads. One can deduce the number of stuck threads by observing the rate at which this increases. |\n| [workqueue_longest_running_processor_seconds](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L76)                           | Gauge     | How many seconds has the longest running processor for workqueue been running.                                                                                                                                                       |\n| [workqueue_retries_total](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/workqueue.go#L83)                                               | Counter   | Total number of retries handled by workqueue.                                                                                                                                                                                        |\n| [rest_client_requests_total ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/metrics/client_go_adapter.go#L33)                                   | Counter   | Number of HTTP requests, partitioned by status code, method, and host.                                                                                                                                                               |\n| [controller_runtime_reconcile_total ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L30)                 | Counter   | Total number of reconciliations per controller.                                                                                                                                                                                      |\n| [controller_runtime_reconcile_errors_total ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L37)          | Counter   | Total number of reconciliation errors per controller.                                                                                                                                                                                |\n| [controller_runtime_terminal_reconcile_errors_total ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L44) | Counter   | Total number of terminal errors from the reconciler.                                                                                                                                                                                 |\n| [controller_runtime_reconcile_time_seconds ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L51)          | Histogram | Length of time per reconciliation per controller.                                                                                                                                                                                    |\n| [controller_runtime_max_concurrent_reconciles ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L60)       | Gauge     | Maximum number of concurrent reconciles per controller.                                                                                                                                                                              |\n| [controller_runtime_active_workers ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/internal/controller/metrics/metrics.go#L67)                  | Gauge     | Number of currently used workers per controller.                                                                                                                                                                                     |\n| [controller_runtime_webhook_latency_seconds ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/webhook/internal/metrics/metrics.go#L31)            | Histogram | Histogram of the latency of processing admission requests.                                                                                                                                                                           |\n| [controller_runtime_webhook_requests_total ](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/webhook/internal/metrics/metrics.go#L40)             | Counter   | Total number of admission requests by HTTP status code.                                                                                                                                                                              |\n| [controller_runtime_webhook_requests_in_flight](https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/webhook/internal/metrics/metrics.go#L51)          | Gauge     | Current number of admission requests being served.                                                                                                                                                                                   |\n"
  },
  {
    "path": "docs/book/src/reference/metrics.md",
    "content": "# Metrics\n\nBy default, controller-runtime builds a global prometheus registry and\npublishes [a collection of performance metrics](/reference/metrics-reference.md) for each controller.\n\n\n<aside class=\"warning\">\n    <h3>IMPORTANT: If you are using `kube-rbac-proxy`</h3>\n\nPlease stop using the image `gcr.io/kubebuilder/kube-rbac-proxy` as soon as possible.\nYour projects will be affected and may fail to work if the image cannot be pulled.\n\n**Images provided under `gcr.io/kubebuilder/` will be unavailable from early 2025.**\n\n- **Projects initialized with Kubebuilder versions `v3.14` or lower** utilize [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy) to protect the metrics endpoint.\nIn this case, you might want to upgrade your project to the latest release or ensure that you have applied the same or similar code changes.\n\n- **However, projects initialized with Kubebuilder versions `v4.1.0` or higher** have similar protection using `authn/authz`\nenabled by default via Controller-Runtime's feature [WithAuthenticationAndAuthorization](https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/metrics/filters#WithAuthenticationAndAuthorization).\n\nIf you want to continue using [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy) then you MUST change\nyour project to use the image from another source.\n\n> For further information, see: [kubebuilder/discussions/3907](https://github.com/kubernetes-sigs/kubebuilder/discussions/3907)\n\n</aside>\n\n## Metrics Configuration\n\nBy looking at the file `config/default/kustomization.yaml` you can\ncheck the metrics are exposed by default:\n\n```yaml\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n```\n\n```yaml\npatches:\n   # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n   # More info: https://book.kubebuilder.io/reference/metrics\n   - path: manager_metrics_patch.yaml\n     target:\n        kind: Deployment\n```\n\nThen, you can check in the `cmd/main.go` where metrics server\nis configured:\n\n```go\n// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n// For more info: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/metrics/server\nMetrics: metricsserver.Options{\n   ...\n},\n```\n\n## Consuming Controller Metrics in Kubebuilder\n\nYou can consume the metrics exposed by the controller using the `curl`\ncommand or any other HTTP client such as Prometheus.\n\nHowever, before doing so, ensure that your client has the\n**required RBAC permissions** to access the `/metrics` endpoint.\n\n### Granting Permissions to Access Metrics\n\nKubebuilder scaffolds a `ClusterRole` with the necessary read permissions under:\n\n```\nconfig/rbac/metrics_reader_role.yaml\n```\n\nThis file contains the required RBAC rules to allow access to the metrics endpoint.\n\n<aside class=\"note\">\n<H1>This ClusterRole is only a helper </H1>\n\nKubebuilder **does not scaffold a RoleBinding or ClusterRoleBinding by default.**\nThis is an intentional design choice to avoid:\n\n- Accidentally binding to the wrong service account,\n- Granting access in restricted environments,\n- Creating conflicts in multi-team or multi-tenant clusters.\n\n</aside>\n\n#### Create a ClusterRoleBinding\n\nYou can create the binding via `kubectl`:\n\n```bash\nkubectl create clusterrolebinding metrics \\\n  --clusterrole=<project-prefix>-metrics-reader \\\n  --serviceaccount=<namespace>:<service-account-name>\n```\n\nOr with a manifest:\n\n```yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: allow-metrics-access\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-reader\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system # Replace 'system' with your controller-manager's namespace\n```\n\n<aside class=\"note\">\n<H1>Why this is manual:</H1>\n\nKubebuilder avoids scaffolding RoleBindings by default because it might:\n - Bind to the wrong ServiceAccount\n - Grant access unnecessarily\n - Cause problems in restricted or multi-tenant clusters\n    - This design provides safety and flexibility, but requires manual binding.\n</aside>\n\n### Testing the Metrics Endpoint (via Curl Pod)\n\nIf you'd like to manually test access to the metrics endpoint, follow these steps:\n\n- Create Role Binding\n\n```bash\nkubectl create clusterrolebinding <project-name>-metrics-binding \\\n  --clusterrole=<project-name>-metrics-reader \\\n  --serviceaccount=<project-name>-system:<project-name>-controller-manager\n```\n\n- Generate a Token\n\n```bash\nexport TOKEN=$(kubectl create token <project-name>-controller-manager -n <project-name>-system)\necho $TOKEN\n```\n\n- Launch Curl Pod\n\n```bash\nkubectl run curl-metrics --rm -it --restart=Never \\\n  --image=curlimages/curl:7.87.0 -n <project-name>-system -- /bin/sh\n```\n\n- Call Metrics Endpoint\n\nInside the pod, use:\n\n```bash\ncurl -v -k -H \"Authorization: Bearer $TOKEN\" \\\n  https://<project-name>-controller-manager-metrics-service.<project-name>-system.svc.cluster.local:8443/metrics\n```\n\n<aside class=\"note\">\n<H1>Notes</H1>\n\n- Replace `<project-name>`, `<namespace>`, and `<service-account-name>` accordingly.\n- Ensure TLS is enabled and certificates are valid if not skipping verification (`-k`).\n\nCheck the options to protect your metrics endpoint in the next sections.\n</aside>\n\n## Metrics Protection and available options\n\nUnprotected metrics endpoints can expose valuable data to unauthorized users,\nsuch as system performance, application behavior, and potentially confidential\noperational metrics. This exposure can lead to security vulnerabilities\nwhere an attacker could gain insights into the system's operation\nand exploit weaknesses.\n\n### By using authn/authz (Enabled by default)\n\nTo mitigate these risks, Kubebuilder projects utilize authentication (authn) and authorization (authz) to protect the\nmetrics endpoint. This approach ensures that only authorized users and service accounts can access sensitive metrics\ndata, enhancing the overall security of the system.\n\nIn the past, the [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy) was employed to provide this protection.\nHowever, its usage has been discontinued in recent versions. Since the release of `v4.1.0`, projects have had the\nmetrics endpoint enabled and protected by default using the [WithAuthenticationAndAuthorization](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/metrics/server)\nfeature provided by controller-runtime.\n\nTherefore, you will find the following configuration:\n\n- In the `cmd/main.go`:\n\n```go\nif secureMetrics {\n  ...\n  metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n}\n```\n\nThis configuration leverages the FilterProvider to enforce authentication and authorization on the metrics endpoint.\nBy using this method, you ensure that the endpoint is accessible only to those with the appropriate permissions.\n\n- In the `config/rbac/kustomization.yaml`:\n\n```yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint.\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n```\n\nIn this way, only Pods using the `ServiceAccount` token are authorized to read the metrics endpoint. For example:\n\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  name: metrics-consumer\n  namespace: system\nspec:\n  # Use the scaffolded service account name to allow authn/authz\n  serviceAccountName: controller-manager\n  containers:\n  - name: metrics-consumer\n    image: curlimages/curl:latest\n    command: [\"/bin/sh\"]\n    args:\n      - \"-c\"\n      - >\n        while true;\n        do\n          # Note here that we are passing the token obtained from the ServiceAccount to curl the metrics endpoint\n          curl -s -k -H \"Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\"\n          https://controller-manager-metrics-service.system.svc.cluster.local:8443/metrics;\n          sleep 60;\n        done\n```\n\n### **(Recommended)** Enabling certificates for Production (Disabled by default)\n\n<aside class=\"warning\">\n<h1>Why Is This Not Enabled by Default?</h1>\n\nThis option is not enabled by default because it introduces a dependency on CertManager.\nTo keep the project as lightweight and beginner-friendly as possible, it is disabled by default.\n\n</aside>\n\n<aside class=\"warning\">\n<h1>Recommended for Production</h1>\n\nThe default scaffold in `cmd/main.go` uses a **controller-runtime feature** to\nautomatically generate a self-signed certificate to secure the metrics server.\nWhile this is convenient for development and testing, it is **not** recommended\nfor production.\n\nThose certificates are used to secure the transport layer (TLS).\nThe token authentication using `authn/authz`, which is enabled by default serves\nas the application-level credential. However, for example, when you enable\nthe integration of your metrics with Prometheus, those certificates can be used\nto secure the communication.\n\n</aside>\n\nProjects built with Kubebuilder releases `4.4.0` and above have the logic scaffolded\nto enable the usage of certificates managed by [CertManager](https://cert-manager.io/)\nfor securing the metrics server. Following the steps below, you can configure your\nproject to use certificates managed by CertManager.\n\n1. **Enable Cert-Manager in `config/default/kustomization.yaml`:**\n    - Uncomment the cert-manager resource to include it in your project:\n\n      ```yaml\n      - ../certmanager\n      ```\n\n2. **Enable the Patch to configure the usage of the certs in the Controller Deployment in `config/default/kustomization.yaml`:**\n    - Uncomment the `cert_metrics_manager_patch.yaml` to mount the `serving-cert` secret in the Manager Deployment.\n\n      ```yaml\n      # Uncomment the patches line if you enable Metrics and CertManager\n      # [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n      # This patch will protect the metrics with certManager self-signed certs.\n      - path: cert_metrics_manager_patch.yaml\n        target:\n          kind: Deployment\n      ```\n3. **Enable the CertManager replaces for the Metrics Server certificates in `config/default/kustomization.yaml`:**\n    - Uncomment the replacements block bellow. It is required to properly set the DNS names for the certificates configured under `config/certmanager`.\n\n      ```yaml\n      # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n      # Uncomment the following replacements to add the cert-manager CA injection annotations\n      #replacements:\n      # - source: # Uncomment the following block to enable certificates for metrics\n      #     kind: Service\n      #     version: v1\n      #     name: controller-manager-metrics-service\n      #     fieldPath: metadata.name\n      #   targets:\n      #     - select:\n      #         kind: Certificate\n      #         group: cert-manager.io\n      #         version: v1\n      #         name: metrics-certs\n      #       fieldPaths:\n      #         - spec.dnsNames.0\n      #         - spec.dnsNames.1\n      #       options:\n      #         delimiter: '.'\n      #         index: 0\n      #         create: true\n      #\n      # - source:\n      #     kind: Service\n      #     version: v1\n      #     name: controller-manager-metrics-service\n      #     fieldPath: metadata.namespace\n      #   targets:\n      #     - select:\n      #         kind: Certificate\n      #         group: cert-manager.io\n      #         version: v1\n      #         name: metrics-certs\n      #       fieldPaths:\n      #         - spec.dnsNames.0\n      #         - spec.dnsNames.1\n      #       options:\n      #         delimiter: '.'\n      #         index: 1\n      #         create: true\n      #\n      ```\n\n4. **Enable the Patch for the `ServiceMonitor` to Use the Cert-Manager-Managed Secret `config/prometheus/kustomization.yaml`:**\n    - Add or uncomment the `ServiceMonitor` patch to securely reference the cert-manager-managed secret, replacing insecure configurations with secure certificate verification:\n\n      ```yaml\n      # [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n      # to securely reference certificates created and managed by cert-manager.\n      # Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n      # to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n      patches:\n        - path: monitor_tls_patch.yaml\n          target:\n            kind: ServiceMonitor\n      ```\n\n    > **NOTE** that the `ServiceMonitor` patch above will ensure that if you enable the Prometheus integration,\n    it will securely reference the certificates created and managed by CertManager. But it will **not** enable the\n    integration with Prometheus. To enable the integration with Prometheus, you need uncomment the `#- ../certmanager`\n    in the `config/default/kustomization.yaml`. For more information, see [Exporting Metrics for Prometheus](#exporting-metrics-for-prometheus).\n\n### **(Optional)** By using Network Policy (Disabled by default)\n\nNetworkPolicy acts as a basic firewall for pods within a Kubernetes cluster, controlling traffic\nflow at the IP address or port level. However, it doesn't handle `authn/authz`.\n\nUncomment the following line in the `config/default/kustomization.yaml`:\n\n```\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which uses webhooks and applied on namespaces labeled 'webhooks: enabled' will be able to work properly.\n#- ../network-policy\n```\n\n## Exporting Metrics for Prometheus\n\nFollow the steps below to export the metrics using the Prometheus Operator:\n\n1. Install Prometheus and Prometheus Operator.\n   We recommend using [kube-prometheus](https://github.com/coreos/kube-prometheus#installing)\n   in production if you don't have your own monitoring system.\n   If you are just experimenting, you can only install Prometheus and Prometheus Operator.\n\n2. Uncomment the line `- ../prometheus` in the `config/default/kustomization.yaml`.\n   It creates the `ServiceMonitor` resource which enables exporting the metrics.\n\n```yaml\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n- ../prometheus\n```\n\nNote that, when you install your project in the cluster, it will create the\n`ServiceMonitor` to export the metrics. To check the ServiceMonitor,\nrun `kubectl get ServiceMonitor -n <project>-system`. See an example:\n\n```\n$ kubectl get ServiceMonitor -n monitor-system\nNAME                                         AGE\nmonitor-controller-manager-metrics-monitor   2m8s\n```\n\n<aside class=\"warning\">\n<h2>If you are using Prometheus Operator ensure that you have the required\npermissions</h2>\n\nIf you are using Prometheus Operator, be aware that, by default, its RBAC\nrules are only enabled for the `default` and `kube-system namespaces`. See its\nguide to know [how to configure kube-prometheus to monitor other namespaces using the `.jsonnet` file](https://github.com/prometheus-operator/kube-prometheus/blob/main/docs/monitoring-other-namespaces.md).\n\nAlternatively, you can give the Prometheus Operator permissions to monitor other namespaces using RBAC. See the Prometheus Operator\n[Enable RBAC rules for Prometheus pods](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#enable-rbac-rules-for-prometheus-pods)\ndocumentation to know how to enable the permissions on the namespace where the\n`ServiceMonitor` and manager exist.\n</aside>\n\nAlso, notice that the metrics are exported by default through port `8443`. In this way,\nyou are able to check the Prometheus metrics in its dashboard. To verify it, search\nfor the metrics exported from the namespace where the project is running\n`{namespace=\"<project>-system\"}`. See an example:\n\n<img width=\"1680\" alt=\"Screenshot 2019-10-02 at 13 07 13\" src=\"https://user-images.githubusercontent.com/7708031/66042888-a497da80-e515-11e9-9d77-d8a9fc1159a5.png\">\n\n## Publishing Additional Metrics\n\nIf you wish to publish additional metrics from your controllers, this\ncan be easily achieved by using the global registry from\n`controller-runtime/pkg/metrics`.\n\nOne way to achieve this is to declare your collectors as global variables and then register them using `init()` in the controller's package.\n\nFor example:\n\n```go\nimport (\n    \"github.com/prometheus/client_golang/prometheus\"\n    \"sigs.k8s.io/controller-runtime/pkg/metrics\"\n)\n\nvar (\n    goobers = prometheus.NewCounter(\n        prometheus.CounterOpts{\n            Name: \"goobers_total\",\n            Help: \"Number of goobers processed\",\n        },\n    )\n    gooberFailures = prometheus.NewCounter(\n        prometheus.CounterOpts{\n            Name: \"goober_failures_total\",\n            Help: \"Number of failed goobers\",\n        },\n    )\n)\n\nfunc init() {\n    // Register custom metrics with the global prometheus registry\n    metrics.Registry.MustRegister(goobers, gooberFailures)\n}\n```\n\nYou may then record metrics to those collectors from any part of your\nreconcile loop. These metrics can be evaluated from anywhere in the operator code.\n\n<aside class=\"note\">\n<h1>Enabling metrics in Prometheus UI</h1>\n\nIn order to publish metrics and view them on the Prometheus UI, the Prometheus instance would have to be configured to select the Service Monitor instance based on its labels.\n\n</aside>\n\nThose metrics will be available for prometheus or\nother openmetrics systems to scrape.\n\n![Screen Shot 2021-06-14 at 10 15 59 AM](https://user-images.githubusercontent.com/37827279/121932262-8843cd80-ccf9-11eb-9c8e-98d0eda80169.png)\n\n<aside class=\"note\">\n<h1>Controller-Runtime Auth/Authz Feature Current Known Limitations and Considerations</h1>\n\nSome known limitations and considerations have been identified. The settings for `cache TTL`, `anonymous access`, and\n`timeouts` are currently hardcoded, which may lead to performance and security concerns due to the inability to\nfine-tune these parameters. Additionally, the current implementation lacks support for configurations like\n`alwaysAllow` for critical paths (e.g., `/healthz`) and `alwaysAllowGroups` (e.g., `system:masters`), potentially\ncausing operational challenges. Furthermore, the system heavily relies on stable connectivity to the `kube-apiserver`,\nmaking it vulnerable to metrics outages during network instability. This can result in the loss of crucial metrics data,\nparticularly during critical periods when monitoring and diagnosing issues in real-time is essential.\n\nAn [issue](https://github.com/kubernetes-sigs/controller-runtime/issues/2781) has been opened to\nenhance the controller-runtime and address these considerations.\n</aside>"
  },
  {
    "path": "docs/book/src/reference/platform.md",
    "content": "# Platforms Supported\n\nKubebuilder produces solutions that by default can work on multiple platforms or specific ones, depending on how you\nbuild and configure your workloads. This guide aims to help you properly configure your projects according to your needs.\n\n## Overview\n\nTo provide support on specific or multiple platforms, you must ensure that all images used in workloads are built to\nsupport the desired platforms. Note that they may not be the same as the platform where you develop your solutions and use KubeBuilder, but instead the platform(s) where your solution should run and be distributed.\nIt is recommended to build solutions that work on multiple platforms so that your project works\non any Kubernetes cluster regardless of the underlying operating system and architecture.\n\n## How to define which platforms are supported\n\nThe following covers what you need to do to provide the support for one or more platforms or architectures.\n\n### 1) Build workload images to provide the support for other platform(s)\n\nThe images used in workloads such as in your Pods/Deployments will need to provide the support for this other platform.\nYou can inspect the images using a ManifestList of supported platforms using the command\n[`docker manifest inspect <image>`][docker-manifest], i.e.:\n\n```shell\n$ docker manifest inspect myregistry/example/myimage:v0.0.1\n{\n   \"schemaVersion\": 2,\n   \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n   \"manifests\": [\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 739,\n         \"digest\": \"sha256:a274a1a2af811a1daf3fd6b48ff3d08feb757c2c3f3e98c59c7f85e550a99a32\",\n         \"platform\": {\n            \"architecture\": \"arm64\",\n            \"os\": \"linux\"\n         }\n      },\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 739,\n         \"digest\": \"sha256:d801c41875f12ffd8211fffef2b3a3d1a301d99f149488d31f245676fa8bc5d9\",\n         \"platform\": {\n            \"architecture\": \"amd64\",\n            \"os\": \"linux\"\n         }\n      },\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 739,\n         \"digest\": \"sha256:f4423c8667edb5372fb0eafb6ec599bae8212e75b87f67da3286f0291b4c8732\",\n         \"platform\": {\n            \"architecture\": \"s390x\",\n            \"os\": \"linux\"\n         }\n      },\n      {\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         \"size\": 739,\n         \"digest\": \"sha256:621288f6573c012d7cf6642f6d9ab20dbaa35de3be6ac2c7a718257ec3aff333\",\n         \"platform\": {\n            \"architecture\": \"ppc64le\",\n            \"os\": \"linux\"\n         }\n      },\n   ]\n}\n```\n\n### 2) (Recommended as a Best Practice) Ensure that node affinity expressions are set to match the supported platforms\n\nKubernetes provides a mechanism called [nodeAffinity][node-affinity] which can be used to limit the possible node\ntargets where a pod can be scheduled. This is especially important to ensure correct scheduling behavior in clusters\nwith nodes that span across multiple platforms (i.e. heterogeneous clusters).\n\n**Kubernetes manifest example**\n\n```yaml\naffinity:\n  nodeAffinity:\n    requiredDuringSchedulingIgnoredDuringExecution:\n      nodeSelectorTerms:\n      - matchExpressions:\n        - key: kubernetes.io/arch\n          operator: In\n          values:\n          - amd64\n          - arm64\n          - ppc64le\n          - s390x\n        - key: kubernetes.io/os\n            operator: In\n            values:\n              - linux\n```\n\n**Golang Example**\n\n```go\nTemplate: corev1.PodTemplateSpec{\n    ...\n    Spec: corev1.PodSpec{\n        Affinity: &corev1.Affinity{\n            NodeAffinity: &corev1.NodeAffinity{\n                RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n                    NodeSelectorTerms: []corev1.NodeSelectorTerm{\n                        {\n                            MatchExpressions: []corev1.NodeSelectorRequirement{\n                                {\n                                    Key:      \"kubernetes.io/arch\",\n                                    Operator: \"In\",\n                                    Values:   []string{\"amd64\"},\n                                },\n                                {\n                                    Key:      \"kubernetes.io/os\",\n                                    Operator: \"In\",\n                                    Values:   []string{\"linux\"},\n                                },\n                            },\n                        },\n                    },\n                },\n            },\n        },\n        SecurityContext: &corev1.PodSecurityContext{\n            ...\n        },\n        Containers: []corev1.Container{{\n            ...\n        }},\n    },\n```\n\n<aside class=\"note\">\n<h1> Example(s) </h1>\n\nYou can look for some code examples by checking the code which is generated via the Deploy\nImage plugin. ([More info](../plugins/available/deploy-image-plugin-v1-alpha.md))\n\n</aside>\n\n## Producing projects that support multiple platforms\n\nYou can use [`docker buildx`][buildx] to cross-compile via emulation ([QEMU](https://www.qemu.org/)) to build the manager image.\nSee that projects scaffold with the latest versions of Kubebuilder have the Makefile target `docker-buildx`.\n\n**Example of Usage**\n\n```shell\n$ make docker-buildx IMG=myregistry/myoperator:v0.0.1\n```\n\nNote that you need to ensure that all images and workloads required and used by your project will provide the same\nsupport as recommended above, and that you properly configure the [nodeAffinity][node-affinity] for all your workloads.\nTherefore, ensure that you uncomment the following code in the `config/manager/manager.yaml` file\n\n```yaml\n# TODO(user): Uncomment the following code to configure the nodeAffinity expression\n# according to the platforms which are supported by your solution.\n# It is considered best practice to support multiple architectures. You can\n# build your manager image using the makefile target docker-buildx.\n# affinity:\n#   nodeAffinity:\n#     requiredDuringSchedulingIgnoredDuringExecution:\n#       nodeSelectorTerms:\n#         - matchExpressions:\n#           - key: kubernetes.io/arch\n#             operator: In\n#             values:\n#               - amd64\n#               - arm64\n#               - ppc64le\n#               - s390x\n#           - key: kubernetes.io/os\n#             operator: In\n#             values:\n#               - linux\n```\n\n<aside class=\"note\">\n<h1>Building images for releases</h1>\n\nYou will probably want to automate the releases of your projects to ensure that the images are always built for the\nsame platforms. Note that Goreleaser also supports [docker buildx][buildx]. See its [documentation][goreleaser-buildx] for more detail.\n\nAlso, you may want to configure GitHub Actions, Prow jobs, or any other solution that you use to build images to\nprovide multi-platform support. Note that you can also use other options like `docker manifest create` to customize\nyour solutions to achieve the same goals with other tools.\n\nBy using Docker and the target provided by default you should NOT change the Dockerfile to use\nany specific GOOS and GOARCH to build the manager binary. However, if you are looking for to\ncustomize the default scaffold and create your own implementations you might want to give\na look in the Golang [doc](https://go.dev/doc/install/source#environment) to knows\nits available options.\n\n</aside>\n\n## Which (workload) images are created by default?\n\nProjects created with the Kubebuilder CLI have two workloads which are:\n\n### Manager\n\nThe container to run the manager implementation is configured in the `config/manager/manager.yaml` file.\nThis image is built with the Dockerfile file scaffolded by default and contains the binary of the project \\\nwhich will be built via the command `go build -a -o manager main.go`.\n\nNote that when you run `make docker-build` OR `make docker-build IMG=myregistry/myprojectname:<tag>`\nan image will be built from the client host (local environment) and produce an image for\nthe client os/arch, which is commonly linux/amd64 or linux/arm64.\n\n<aside class=\"note\">\n<h1>Mac Os</h1>\n\nIf you are running from an Mac Os environment then, Docker also will consider it as linux/$arch. Be aware that\nwhen, for example, is running Kind on a Mac OS operational system the nodes will\nend up labeled with ` kubernetes.io/os=linux`\n\n</aside>\n\n[node-affinity]: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n[docker-manifest]: https://docs.docker.com/engine/reference/commandline/manifest/\n[buildx]: https://docs.docker.com/build/buildx/\n[goreleaser-buildx]: https://goreleaser.com/customization/docker/#use-a-specific-builder-with-docker-buildx\n"
  },
  {
    "path": "docs/book/src/reference/pprof-tutorial.md",
    "content": "# Monitoring Performance with Pprof\n\n[Pprof][github], a Go profiling tool, helps identify performance bottlenecks in areas like CPU and memory usage. It's integrated with the controller-runtime library's HTTP server, enabling profiling via HTTP endpoints. You can visualize the data using go tool pprof. Since [Pprof][github] is built into controller-runtime, no separate installation is needed. [Manager options][manager-options-doc] make it easy to enable pprof and gather runtime metrics to optimize controller performance.\n\n<aside class=\"warning\">\n    <h3>Not Recommended for Production</h3>\n\nWhile [Pprof][github] is an excellent tool for profiling and debugging, it is not recommended to leave it enabled in production environments. The primary reasons are:\n\n1. **Security Risk**: The profiling endpoints expose detailed information about your application's performance and resource usage, which could be exploited if accessed by unauthorized users.\n2. **Overhead**: Running profiling can introduce performance overhead, mainly CPU usage, especially under heavy load, potentially impacting production workloads.\n\n</aside>\n\n## How to use Pprof?\n\n1. **Enabling Pprof**\n\n    In your `cmd/main.go` file, add the field:\n\n    ```golang\n    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n      ...\n      // PprofBindAddress is the TCP address that the controller should bind to\n      // for serving pprof. Specify the manager address and the port that should be bind.\n      PprofBindAddress:       \":8082\",\n      ...\n    })\n    ```\n\n2. **Test It Out**\n\n    After enabling [Pprof][github], you need to build and deploy your controller to test it out. Follow the steps in the [Quick Start guide][quick-start-run-it] to run your project locally or on a cluster.\n\n    Then, you can apply your CRs/samples in order to monitor the performance of its controllers.\n\n3. **Exporting the data**\n\n    Using `curl`, export the profiling statistics to a file like this:\n\n    ```bash\n    # Note that we are using the bind host and port configured via the\n    # Manager Options in the cmd/main.go\n    curl -s \"http://127.0.0.1:8082/debug/pprof/profile\" > ./cpu-profile.out\n    ```\n\n4. **Visualizing the results on Browser**\n\n    ```bash\n    # Go tool will open a session on port 8080.\n    # You can change this as per your own need.\n    go tool pprof -http=:8080 ./cpu-profile.out\n    ```\n\n    Visualization results will vary depending on the deployed workload, and the Controller's behavior.\n    However, you'll see the result on your browser similar to this one:\n\n    ![pprof-result-visualization](./images/pprof-result-visualization.png)\n\n[manager-options-doc]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager\n[quick-start-run-it]: ../quick-start.md#test-it-out\n[github]: https://github.com/google/pprof"
  },
  {
    "path": "docs/book/src/reference/project-config.md",
    "content": "# Project Config\n\n## Overview\n\nThe Project Config represents the configuration of a KubeBuilder project. All projects that are scaffolded with the CLI (KB version 3.0 and higher) will generate the `PROJECT` file in the projects' root directory. Therefore, it will store all plugins and input data used to generate the project and APIs to better enable plugins to make useful decisions when scaffolding.\n\n## Example\n\nFollowing is an example of a PROJECT config file which is the result of a project generated with two APIs using the [Deploy Image Plugin][deploy-image-plugin].\n\n```yaml\n# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ndomain: testproject.org\ncliVersion: v4.6.0\nlayout:\n  - go.kubebuilder.io/v4\nplugins:\n  deploy-image.go.kubebuilder.io/v1-alpha:\n    resources:\n      - domain: testproject.org\n        group: example.com\n        kind: Memcached\n        options:\n          containerCommand: memcached,--memory-limit=64,-o,modern,-v\n          containerPort: \"11211\"\n          image: memcached:1.4.36-alpine\n          runAsUser: \"1001\"\n        version: v1alpha1\n      - domain: testproject.org\n        group: example.com\n        kind: Busybox\n        options:\n          image: busybox:1.28\n        version: v1alpha1\nprojectName: project-v4-with-deploy-image\nrepo: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image\nresources:\n  - api:\n      crdVersion: v1\n      namespaced: true\n    controller: true\n    domain: testproject.org\n    group: example.com\n    kind: Memcached\n    path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1\n    version: v1alpha1\n    webhooks:\n      validation: true\n      webhookVersion: v1\n  - api:\n      crdVersion: v1\n      namespaced: true\n    controller: true\n    domain: testproject.org\n    group: example.com\n    kind: Busybox\n    path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1\n    version: v1alpha1\n  - controller: true\n    domain: io\n    external: true\n    group: cert-manager\n    kind: Certificate\n    path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n    module: github.com/cert-manager/cert-manager@v1.18.2\n    version: v1\nversion: \"3\"\n```\n## Why do we need to store the plugins and data used?\n\nFollowing some examples of motivations to track the input used:\n- check if a plugin can or cannot be scaffolded on top of an existing plugin (i.e.) plugin compatibility while chaining multiple of them together.\n- what operations can or cannot be done such as verify if the layout allow API(s) for different groups to be scaffolded for the current configuration or not.\n- verify what data can or not be used in the CLI operations such as to ensure that WebHooks can only be created for pre-existent API(s)\n\nNote that KubeBuilder is not only a CLI tool but can also be used as a library to allow users to create their plugins/tools, provide helpers and customizations on top of their existing projects - an example of which is [Operator-SDK][operator-sdk]. SDK leverages KubeBuilder to create plugins to allow users to work with other languages and provide helpers for their users to integrate their projects with, for example, the [Operator Framework solutions/OLM][olm]. You can check the [plugin's documentation][plugins-doc] to know more about creating custom plugins.\n\nAdditionally, another motivation for the PROJECT file is to help us to create a feature that allows users to easily upgrade their projects by providing helpers that automatically re-scaffold the project. By having all the required metadata regarding the APIs, their configurations and versions in the PROJECT file. For example, it can be used to automate the process of re-scaffolding while migrating between plugin versions. ([More info][doc-design-helper]).\n\n## Versioning\n\nThe Project config is versioned according to its layout. For further information see [Versioning][versioning].\n\n## Layout Definition\n\nThe `PROJECT` version `3` layout looks like:\n\n```yaml\ndomain: testproject.org\ncliVersion: v4.6.0\nlayout:\n  - go.kubebuilder.io/v4\nplugins:\n  deploy-image.go.kubebuilder.io/v1-alpha:\n    resources:\n      - domain: testproject.org\n        group: example.com\n        kind: Memcached\n        options:\n          containerCommand: memcached,--memory-limit=64,-o,modern,-v\n          containerPort: \"11211\"\n          image: memcached:memcached:1.6.26-alpine3.19\n          runAsUser: \"1001\"\n        version: v1alpha1\n      - domain: testproject.org\n        group: example.com\n        kind: Busybox\n        options:\n          image: busybox:1.36.1\n        version: v1alpha1\nprojectName: project-v4-with-deploy-image\nrepo: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image\nresources:\n  - api:\n      crdVersion: v1\n      namespaced: true\n    controller: true\n    domain: testproject.org\n    group: example.com\n    kind: Memcached\n    path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1\n    version: v1alpha1\n    webhooks:\n      validation: true\n      webhookVersion: v1\n  - api:\n      crdVersion: v1\n      namespaced: true\n    controller: true\n    domain: testproject.org\n    group: example.com\n    kind: Busybox\n    path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1\n    version: v1alpha1\n  - controller: true\n    domain: io\n    external: true\n    group: cert-manager\n    kind: Certificate\n    path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n    module: github.com/cert-manager/cert-manager@v1.18.2\n    version: v1\nversion: \"3\"\n```\n\nNow let's check its layout fields definition:\n\n| Field                               | Description                                                                                                                                                                                                                                                                     |\n|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `cliVersion`                | Used to record the specific CLI version used during project scaffolding with `init`. Helps identifying the version of the tooling employed, aiding in troubleshooting and ensuring compatibility with updates.                                           |\n| `layout`                            | Defines the global plugins, e.g. a project `init` with `--plugins=\"go/v4,deploy-image/v1-alpha\"` means that any sub-command used will always call its implementation for both plugins in a chain.                                                                               |\n| `domain`                            | Store the domain of the project. This information can be provided by the user when the project is generate with the `init` sub-command and the `domain` flag.                                                                                                                   |\n| `plugins`                           | Defines the plugins used to do custom scaffolding, e.g. to use the optional `deploy-image/v1-alpha` plugin to do scaffolding for just a specific api via the command `kubebuider create api [options] --plugins=deploy-image/v1-alpha`.                                         |\n| `projectName`                       | The name of the project. This will be used to scaffold the manager data. By default it is the name of the project directory, however, it can be provided by the user in the `init` sub-command via the `--project-name` flag.                                                   |\n| `repo`                              | The project repository which is the Golang module, e.g `github.com/example/myproject-operator`.                                                                                                                                                                                 |\n| `multigroup`                        | **(Optional)** When set to `true`, enables multi-group project layout. APIs are organized into group-specific directories (`api/<group>/<version>/`). Can be set during initialization via `kubebuilder init --multigroup` or enabled/disabled later via `kubebuilder edit --multigroup`. Default is `false` (omitted from PROJECT file). |\n| `namespaced`                        | **(Optional)** When set to `true`, configures the project for namespace-scoped deployment. The operator will only watch and manage resources within its deployment namespace, using namespace-scoped RBAC (`Role`/`RoleBinding` instead of `ClusterRole`/`ClusterRoleBinding`). Can be enabled/disabled via `kubebuilder edit --namespaced`. Default is `false` (cluster-scoped, omitted from PROJECT file). |\n| `resources`                         | An array of all resources which were scaffolded in the project.                                                                                                                                                                                                                 |\n| `resources.api`                     | The API scaffolded in the project via the sub-command `create api`.                                                                                                                                                                                                             |\n| `resources.api.crdVersion`          | The Kubernetes API version (`apiVersion`) used to do the scaffolding for the CRD resource.                                                                                                                                                                                      |\n| `resources.api.namespaced`          | The API RBAC permissions which can be namespaced or cluster scoped.                                                                                                                                                                                                             |\n| `resources.controller`              | Indicates whether a controller was scaffolded for the API.                                                                                                                                                                                                                      |\n| `resources.domain`                  | The domain of the resource which was provided by the `--domain` flag when the project was initialized or via the flag `--external-api-domain` when it was used to scaffold controllers for an [External Type][external-type].                                                   |\n| `resources.group`                   | The GKV group of the resource which is provided by the `--group` flag when the sub-command `create api` is used.                                                                                                                                                                |\n| `resources.version`                 | The GKV version of the resource which is provided by the `--version` flag when the sub-command `create api` is used.                                                                                                                                                            |\n| `resources.kind`                    | Store GKV Kind of the resource which is provided by the `--kind` flag when the sub-command `create api` is used.                                                                                                                                                                |\n| `resources.path`                    | The import path for the API resource. It will be `<repo>/api/<kind>` unless the API added to the project is an external or core-type. For the core-types scenarios, the paths used are mapped [here][core-types]. Or either the path informed by the flag `--external-api-path` |\n| `resources.core`                    | It is `true` when  the group used is from Kubernetes API and the API resource is not defined on the project.                                                                                                                                                                    |\n| `resources.external`                | It is `true` when  the flag `--external-api-path` was used to generated the scaffold for an [External Type][external-type].                                                                                                                                                     |\n| `resources.module`                  | **(Optional)** The Go module path for external API dependencies, optionally including a version (e.g., `github.com/cert-manager/cert-manager@v1.18.2` or just `github.com/cert-manager/cert-manager`). Only used when `external` is `true`. Provided via the `--external-api-module` flag to explicitly pin a specific version in `go.mod` or to specify the module when it cannot be automatically determined from `--external-api-path`. If not provided, `go mod tidy` will resolve the dependency automatically. |\n| `resources.webhooks`                | Store the webhooks data when the sub-command `create webhook` is used.                                                                                                                                                                                                          |\n| `resources.webhooks.spoke`          | Store the API version that will act as the Spoke with the designated Hub version for conversion webhooks.                                                                                                                                                                       |\n| `resources.webhooks.webhookVersion` | The Kubernetes API version (`apiVersion`) used to scaffold the webhook resource.                                                                                                                                                                                                |\n| `resources.webhooks.conversion`     | It is `true` when the webhook was scaffold with the `--conversion` flag which means that is a conversion webhook.                                                                                                                                                               |\n| `resources.webhooks.defaulting`     | It is `true` when the webhook was scaffold with the `--defaulting` flag which means that is a defaulting webhook.                                                                                                                                                               |\n| `resources.webhooks.validation`     | It is `true` when the webhook was scaffold with the `--programmatic-validation` flag which means that is a validation webhook.                                                                                                                                                  |\n\n[project]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/testdata/project-v3/PROJECT\n[versioning]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/VERSIONING.md#Versioning\n[core-types]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/pkg/plugins/golang/options.go\n[deploy-image-plugin]: ../plugins/available/deploy-image-plugin-v1-alpha.md\n[olm]: https://olm.operatorframework.io/\n[plugins-doc]: ../plugins/creating-plugins.html#why-use-the-kubebuilder-style\n[doc-design-helper]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/helper_to_upgrade_projects_by_rescaffolding.md\n[operator-sdk]: https://sdk.operatorframework.io/\n[external-type]: ./using_an_external_resource.md"
  },
  {
    "path": "docs/book/src/reference/raising-events.md",
    "content": "# Creating Events\n\nIt is often useful to publish *Event* objects from the controller Reconcile function as they allow users or any automated processes to see what is going on with a particular object and respond to them.\n\n Recent Events for an object can be viewed by running `$ kubectl describe <resource kind> <resource name>`. Also, they can be checked by running `$ kubectl get events`.\n\n<aside class=\"warning\">\n<h1>Events should be raised in certain circumstances only</h1>\n\nBe aware that it is **not** recommended to emit Events for all operations. If authors raise too many events, it brings bad UX experiences for those consuming the solutions on the cluster, and they may find it difficult to filter an actionable event from the cluster. For more information, please take a look at the [Kubernetes APIs convention][Events].\n\n</aside>\n\n## Writing Events\n\nAnatomy of an Event:\n\n```go\nEventf(regarding, related runtime.Object, eventtype, reason, action, message string, args ...interface{})\n```\n\n- `regarding` is the object this event is about.\n- `related` is an optional secondary object related to this event (use `nil` if not applicable).\n- `eventtype` is this event type, and is either *Normal* or *Warning*. ([More info][Event-Example])\n- `reason` is the reason this event is generated. It should be short and unique with `UpperCamelCase` format. The value could appear in *switch* statements by automation. ([More info][Reason-Example])\n- `action` is the action that was taken/failed regarding the object.\n- `message` is a human-readable description with optional format arguments.\n\n\n\n<aside class=\"note\">\n<h1>Example Usage</h1>\n\nFollowing is an example of a code implementation that raises an Event.\n\n```go\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name, cr.Namespace)\n```\n\n</aside>\n\n### How to be able to raise Events?\n\nFollowing are the steps with examples to help you raise events in your controller's reconciliations.\nEvents are published from a Controller using an [EventRecorder][Events]`type CorrelatorOptions struct`,\nwhich can be created for a Controller by calling `GetEventRecorder(name string)` on a Manager. See that we will change the implementation scaffolded in `cmd/main.go`:\n\n```go\n\tif err := (&controller.MyKindReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\t// Note that we added the following line:\n\t\tRecorder: mgr.GetEventRecorder(\"mykind-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"MyKind\")\n\t\tos.Exit(1)\n\t}\n```\n\n### Allowing usage of EventRecorder on the Controller\n\nTo raise an event, you must have access to `events.EventRecorder` in the Controller.  Therefore, firstly let's update the controller implementation:\n```go\nimport (\n\t...\n\t\"k8s.io/client-go/tools/events\"\n\t...\n)\n// MyKindReconciler reconciles a MyKind object\ntype MyKindReconciler struct {\n\tclient.Client\n\tScheme   *runtime.Scheme\n\t// See that we added the following code to allow us to pass the events.EventRecorder\n\tRecorder events.EventRecorder\n}\n```\n### Passing the EventRecorder to the Controller\n\nEvents are published from a Controller using an [EventRecorder]`type CorrelatorOptions struct`,\nwhich can be created for a Controller by calling `GetEventRecorder(name string)` on a Manager. See that we will change the implementation scaffolded in `cmd/main.go`:\n\n```go\n\tif err := (&controller.MyKindReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\t// Note that we added the following line:\n\t\tRecorder: mgr.GetEventRecorder(\"mykind-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"MyKind\")\n\t\tos.Exit(1)\n\t}\n```\n\n### Granting the required permissions\n\nYou must also grant the RBAC rules permissions to allow your project to create Events. Therefore, ensure that you add the [RBAC][rbac-markers] into your controller:\n\n```go\n...\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n...\nfunc (r *MyKindReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n```\n\nAnd then, run `$ make manifests` to update the rules under `config/rbac/role.yaml`.\n\n[Events]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#events\n[Event-Example]: https://github.com/kubernetes/api/blob/6c11c9e4685cc62e4ddc8d4aaa824c46150c9148/core/v1/types.go#L6019-L6024\n[Reason-Example]: https://github.com/kubernetes/api/blob/6c11c9e4685cc62e4ddc8d4aaa824c46150c9148/core/v1/types.go#L6048\n[Message-Example]: https://github.com/kubernetes/api/blob/6c11c9e4685cc62e4ddc8d4aaa824c46150c9148/core/v1/types.go#L6053\n[rbac-markers]: ./markers/rbac.md\n"
  },
  {
    "path": "docs/book/src/reference/reference.md",
    "content": "# Reference\n\n  - [Generating CRDs](generating-crd.md)\n  - [Using Finalizers](using-finalizers.md)\n    Finalizers are a mechanism to\n    execute any custom logic related to a resource before it gets deleted from\n    Kubernetes cluster.\n  - [Watching Resources](watching-resources.md)\n    Watch resources in the Kubernetes cluster to be informed and take actions on changes.\n      - [Watching Secondary Resources that are `Owned` ](watching-resources/secondary-owned-resources.md)\n      - [Watching Secondary Resources that are NOT `Owned`](watching-resources/secondary-resources-not-owned)\n      - [Using Predicates to Refine Watches](watching-resources/predicates-with-watch.md)\n  - [Kind cluster](kind.md)\n  - [What's a webhook?](webhook-overview.md)\n    Webhooks are HTTP callbacks, there are 3\n    types of webhooks in k8s: 1) admission webhook 2) CRD conversion webhook 3)\n    authorization webhook\n    - [Admission webhook](admission-webhook.md)\n      Admission webhooks are HTTP\n      callbacks for mutating or validating resources before the API server admit\n      them.\n  - [Markers for Config/Code Generation](markers.md)\n\n      - [CRD Generation](markers/crd.md)\n      - [CRD Validation](markers/crd-validation.md)\n      - [Webhook](markers/webhook.md)\n      - [Object/DeepCopy](markers/object.md)\n      - [RBAC](markers/rbac.md)\n      - [Scaffold](markers/scaffold.md)\n\n  - [Monitoring with Pprof](pprof-tutorial.md)\n  - [controller-gen CLI](controller-gen.md)\n  - [completion](completion.md)\n  - [Artifacts](artifacts.md)\n  - [Platform Support](platform.md)\n\n  - [Sub-Module Layouts](submodule-layouts.md)\n  - [Using an external Resource / API](using_an_external_resource.md)\n\n  - [Metrics](metrics.md)\n      - [Reference](metrics-reference.md)\n\n  - [CLI plugins](../plugins/plugins.md)\n"
  },
  {
    "path": "docs/book/src/reference/scopes.md",
    "content": "# Understanding Scopes in Kubebuilder\n\nIn Kubernetes, **scope** defines the boundaries within which a resource or controller operates.\n\nWhen building with Kubebuilder, you work with two independent scoping concepts:\n\n1. **[Manager Scope](./manager-scope.md)** - Determines which namespace(s) your manager watches and operates in\n2. **[CRD Scope](./crd-scope.md)** - Determines whether your custom resources are namespace-specific or cluster-wide\n\n## What is Scope?\n\nScope defines the visibility and access boundaries in a Kubernetes cluster:\n\n- **Cluster-scoped**: Operates across the entire cluster with access to all namespaces\n- **Namespace-scoped**: Limited to specific namespace(s) for isolation and security\n\n## Manager Scope vs CRD Scope\n\nThese concepts are **independent** and configured separately:\n\n- **Manager Scope**: Controls which namespace(s) the manager watches (configured via deployment RBAC and cache)\n- **CRD Scope**: Controls whether custom resources are namespace-specific or cluster-wide (configured in CRD manifest)\n\nYou can combine them in different ways - for example, a cluster-scoped manager can manage namespace-scoped CRDs (the default pattern).\n\n## Learn More\n\nFor detailed information, configuration steps, and code examples:\n\n- **[Manager Scope](./manager-scope.md)** - Manager scope configuration, RBAC, cache setup, and namespace watching\n- **[CRD Scope](./crd-scope.md)** - CRD scope configuration, markers, and RBAC considerations\n- **[Migrating to Namespace-Scoped Manager](../migration/namespace-scoped.md)** - Step-by-step migration guide for existing projects\n"
  },
  {
    "path": "docs/book/src/reference/submodule-layouts.md",
    "content": "# Sub-Module Layouts\n\nThis part describes how to modify a scaffolded project for use with multiple `go.mod` files for APIs and Controllers.\n\nSub-Module Layouts (in a way you could call them a special form of [Monorepo's][monorepo]) are a special use case and can help in scenarios that involve reuse of APIs without introducing indirect dependencies that should not be available in the project consuming the API externally.\n\n<aside class=\"note\">\n<h1>Using External Resources/APIs</h1>\n\nIf you are looking to do operations and reconcile via a controller a Type(CRD) which are owned by another project\nor By Kubernetes API then, please see [Using an external Resources/API](/reference/using_an_external_type.md)\nfor more info.\n\n</aside>\n\n## Overview\n\nSeparate `go.mod` modules for APIs and Controllers can help for the following cases:\n\n- There is an enterprise version of an operator available that wants to reuse APIs from the Community Version\n- There are many (possibly external) modules depending on the API and you want to have a more strict separation of transitive dependencies\n- If you want to reduce impact of transitive dependencies on your API being included in other projects\n- If you are looking to separately manage the lifecycle of your API release process from your controller release process.\n- If you are looking to modularize your codebase without splitting your code between multiple repositories.\n\nThey introduce however multiple caveats into typical projects which is one of the main factors that makes them hard to recommend in a generic use-case or plugin:\n\n- Multiple `go.mod` modules are not recommended as a go best practice and [multiple modules are mostly discouraged][multi-module-repositories]\n- There is always the possibility to extract your APIs into a new repository and arguably also have more control over the release process in a project spanning multiple repos relying on the same API types.\n- It requires at least one [replace directive][replace-directives] either through `go.work` which is at least 2 more files plus an environment variable for build environments without GO_WORK or through `go.mod` replace, which has to be manually dropped and added for every release.\n\n<aside class=\"warning\">\n    <h3>Implications on Maintenance efforts</h3>\n\nWhen deciding to deviate from the standard kubebuilder `PROJECT` setup or the extended layouts offered by its plugins, it can result in increased maintenance overhead as there can be breaking changes in upstream that could break with the custom module structure described here.\n\nSplitting your codebase to multiple repos and/or multiple modules incurs costs that will grow over time. You'll need to define clear version dependencies between your own modules, do phased upgrades carefully, etc. Especially for small-to-medium projects, one repo and one module is the best way to go.\n\nBear in mind, that it is not recommended to deviate from the proposed layout unless you know what you are doing.\nYou may also lose the ability to use some of the CLI features and helpers. For further information on the project layout, see the doc [What's in a basic project?][basic-project-doc]\n\n</aside>\n\n## Adjusting your Project\n\nFor a proper Sub-Module layout, we will use the generated APIs as a starting point.\n\nFor the steps below, we will assume you created your project in your `GOPATH` with\n\n```shell\nkubebuilder init\n```\n\nand created an API & controller with\n\n```shell\nkubebuilder create api --group operator --version v1alpha1 --kind Sample --resource --controller --make\n```\n\n### Creating a second module for your API\n\nNow that we have a base layout in place, we will enable you for multiple modules.\n\n1. Navigate to `api/v1alpha1`\n2. Run `go mod init` to create a new submodule\n3. Run `go mod tidy` to resolve the dependencies\n\nYour api go.mod file could now look like this:\n\n```go.mod\nmodule YOUR_GO_PATH/test-operator/api/v1alpha1\n\ngo 1.21.0\n\nrequire (\n        k8s.io/apimachinery v0.28.4\n        sigs.k8s.io/controller-runtime v0.16.3\n)\n\nrequire (\n        github.com/go-logr/logr v1.2.4 // indirect\n        github.com/gogo/protobuf v1.3.2 // indirect\n        github.com/google/gofuzz v1.2.0 // indirect\n        github.com/json-iterator/go v1.1.12 // indirect\n        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n        github.com/modern-go/reflect2 v1.0.2 // indirect\n        golang.org/x/net v0.17.0 // indirect\n        golang.org/x/text v0.13.0 // indirect\n        gopkg.in/inf.v0 v0.9.1 // indirect\n        gopkg.in/yaml.v2 v2.4.0 // indirect\n        k8s.io/klog/v2 v2.100.1 // indirect\n        k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect\n        sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n        sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n)\n```\n\nAs you can see it only includes apimachinery and controller-runtime as dependencies and any dependencies you have\ndeclared in your controller are not taken over into the indirect imports.\n\n### Using replace directives for development\n\nWhen trying to resolve your main module in the root folder of the operator, you will notice an error if you use a VCS path:\n\n```shell\ngo mod tidy\ngo: finding module for package YOUR_GO_PATH/test-operator/api/v1alpha1\nYOUR_GO_PATH/test-operator imports\n\tYOUR_GO_PATH/test-operator/api/v1alpha1: cannot find module providing package YOUR_GO_PATH/test-operator/api/v1alpha1: module YOUR_GO_PATH/test-operator/api/v1alpha1: git ls-remote -q origin in LOCALVCSPATH: exit status 128:\n\tremote: Repository not found.\n\tfatal: repository 'https://YOUR_GO_PATH/test-operator/' not found\n```\n\nThe reason for this is that you may have not pushed your modules into the VCS yet and resolving the main module will fail as it can no longer\ndirectly access the API types as a package but only as a module.\n\nTo solve this issue, we will have to tell the go tooling to properly `replace` the API module with a local reference to your path.\n\nYou can do this with 2 different approaches: go modules and go workspaces.\n\n#### Using go modules\n\nFor go modules, you will edit the main `go.mod` file of your project and issue a replace directive.\n\nYou can do this by editing the `go.mod` with\n``\n```shell\ngo mod edit -require YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0 # Only if you didn't already resolve the module\ngo mod edit -replace YOUR_GO_PATH/test-operator/api/v1alpha1@v0.0.0=./api/v1alpha1\ngo mod tidy\n```\n\nNote that we used the placeholder version `v0.0.0` of the API Module. In case you already released your API module once,\nyou can use the real version as well. However this will only work if the API Module is already available in the VCS.\n\n<aside class=\"warning\">\n    <h3>Implications on controller releases</h3>\n\nSince the main `go.mod` file now has a replace directive, it is important to drop it again before releasing your controller module.\nTo achieve this you can simply run\n\n```shell\ngo mod edit -dropreplace YOUR_GO_PATH/test-operator/api/v1alpha1\ngo mod tidy\n```\n\n</aside>\n\n#### Using go workspaces\n\nFor go workspaces, you will not edit the `go.mod` files yourself, but rely on the workspace support in go.\n\nTo initialize a workspace for your project, run `go work init` in the project root.\n\nNow let us include both modules in our workspace:\n```shell\ngo work use . # This includes the main module with the controller\ngo work use api/v1alpha1 # This is the API submodule\ngo work sync\n```\n\nThis will lead to commands such as `go run` or `go build` to respect the workspace and make sure that local resolution is used.\n\nYou will be able to work with this locally without having to build your module.\n\nWhen using `go.work` files, it is recommended to not commit them into the repository and add them to `.gitignore`.\n\n```gitignore\ngo.work\ngo.work.sum\n```\n\nWhen releasing with a present `go.work` file, make sure to set the environment variable `GOWORK=off` (verifiable with `go env GOWORK`) to make sure the release process does not get impeded by a potentially committed `go.work` file.\n\n#### Adjusting the Dockerfile\n\nWhen building your controller image, kubebuilder by default is not able to work with multiple modules.\nYou will have to manually add the new API module into the download of dependencies:\n\n```dockerfile\n# Build the manager binary\nFROM docker.io/golang:1.20 as builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# Copy the Go Sub-Module manifests\nCOPY api/v1alpha1/go.mod api/go.mod\nCOPY api/v1alpha1/go.sum api/go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the go source\nCOPY cmd/main.go cmd/main.go\nCOPY api/ api/\nCOPY internal/controller/ internal/controller/\n\n# Build\n# the GOARCH has not a default value to allow the binary be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n```\n\n### Creating a new API and controller release\n\nBecause you adjusted the default layout, before releasing your first version of your operator, make sure to [familiarize yourself with mono-repo/multi-module releases][multi-module-repositories] with multiple `go.mod` files in different subdirectories.\n\nAssuming a single API was created, the release process could look like this:\n\n```sh\ngit commit\ngit tag v1.0.0 # this is your main module release\ngit tag api/v1.0.0 # this is your api release\ngo mod edit -require YOUR_GO_PATH/test-operator/api@v1.0.0 # now we depend on the api module in the main module\ngo mod edit -dropreplace YOUR_GO_PATH/test-operator/api/v1alpha1 # this will drop the replace directive for local development in case you use go modules, meaning the sources from the VCS will be used instead of the ones in your monorepo checked out locally.\ngit push origin main v1.0.0 api/v1.0.0\n```\n\nAfter this, your modules will be available in VCS and you do not need a local replacement anymore. However if you're making local changes,\nmake sure to adopt your behavior with `replace` directives accordingly.\n\n### Reusing your extracted API module\n\nWhenever you want to reuse your API module with a separate kubebuilder, we will assume you follow the guide for [using an external Type](/reference/using_an_external_type.md).\nWhen you get to the step `Edit the API files` simply import the dependency with\n\n```shell\ngo get YOUR_GO_PATH/test-operator/api@v1.0.0\n```\n\nand then use it as explained in the guide.\n\n[basic-project-doc]: ./../cronjob-tutorial/basic-project.md\n[monorepo]: https://en.wikipedia.org/wiki/Monorepo\n[replace-directives]: https://go.dev/ref/mod#go-mod-file-replace\n[multi-module-repositories]: https://github.com/golang/go/wiki/Modules#faqs--multi-module-repositories\n"
  },
  {
    "path": "docs/book/src/reference/using-finalizers.md",
    "content": "# Using Finalizers\n\n`Finalizers` allow controllers to implement asynchronous pre-delete hooks. Let's\nsay you create an external resource (such as a storage bucket) for each object of\nyour API type, and you want to delete the associated external resource\non object's deletion from Kubernetes, you can use a finalizer to do that.\n\nYou can read more about the finalizers in the [Kubernetes reference docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers). The section below demonstrates how to register and trigger pre-delete hooks\nin the `Reconcile` method of a controller.\n\nThe key point to note is that a finalizer causes \"delete\" on the object to become\nan \"update\" to set deletion timestamp. Presence of deletion timestamp on the object\nindicates that it is being deleted. Otherwise, without finalizers, a delete\nshows up as a reconcile where the object is missing from the cache.\n\nHighlights:\n- If the object is not being deleted and does not have the finalizer registered,\n  then add the finalizer and update the object in Kubernetes.\n- If object is being deleted and the finalizer is still present in finalizers list,\n  then execute the pre-delete logic and remove the finalizer and update the\n  object.\n- Ensure that the pre-delete logic is idempotent.\n\n{{#literatego ../cronjob-tutorial/testdata/finalizer_example.go}}\n\n"
  },
  {
    "path": "docs/book/src/reference/using_an_external_resource.md",
    "content": "# Using External Resources\n\nIn some cases, your project may need to work with resources that aren't defined by your own APIs.\nThese external resources fall into two main categories:\n\n- **Core Types**: API types defined by Kubernetes itself, such as `Pods`, `Services`, and `Deployments`.\n- **External Types**: API types defined in other projects, such as CRDs defined by another solution.\n\n## Managing External Types\n\n### Creating a Controller for External Types\n\nTo create a controller for an external type without scaffolding a resource,\nuse the `create api` command with the `--resource=false` option and specify the path to the\nexternal API type using the `--external-api-path` and `--external-api-domain` flag options.\nThis generates a controller for types defined outside your project,\nsuch as CRDs managed by other Operators.\n\nThe command looks like this:\n\n```shell\nkubebuilder create api --group <theirgroup> --version <theirversion> --kind <theirKind> --controller --resource=false --external-api-path=<their Golang path import> --external-api-domain=<theirdomain>\n```\n\n- `--external-api-path`: Provide the Go import path where the external types are defined.\n- `--external-api-domain`:  Provide the domain for the external types. This value will be used to generate RBAC permissions and create the QualifiedGroup, such as - `apiGroups: <group>.<domain>`\n\nFor example, if you're managing Certificates from Cert Manager:\n\n```shell\nkubebuilder create api --group certmanager --version v1 --kind Certificate --controller=true --resource=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io\n```\n\n<aside class=\"note\">\n<h1>Pinning External API Versions</h1>\n\nYou can pin a specific version of the external API dependency using the `--external-api-module` flag:\n\n```shell\nkubebuilder create api --group certmanager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager@v1.18.2\n```\n\nThe flag accepts the module path with optional version (e.g., `github.com/cert-manager/cert-manager@v1.18.2`).\nThe module is stored in the PROJECT file and added to `go.mod` using `go get`,\nwhich cleanly adds it as a direct dependency without polluting go.mod with unnecessary indirect dependencies.\n\n</aside>\n\nSee the RBAC [markers][markers-rbac] generated for this:\n\n```go\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/finalizers,verbs=update\n```\n\nAlso, the RBAC role:\n\n```ymal\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/status\n  verbs:\n  - get\n  - patch\n  - update\n```\n\nThis scaffolds a controller for the external type but skips creating new resource\ndefinitions since the type is defined in an external project.\n\n### Creating a Webhook to Manage an External Type\n\nYou can create webhooks for external types by providing the external API path, domain, and optionally the module:\n\n```shell\nkubebuilder create webhook --group certmanager --version v1 --kind Issuer \\\n  --defaulting --programmatic-validation \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=cert-manager.io\n```\n\nYou can also pin the version using the `--external-api-module` flag:\n\n```shell\nkubebuilder create webhook --group certmanager --version v1 --kind Issuer \\\n  --defaulting --programmatic-validation \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=cert-manager.io \\\n  --external-api-module=github.com/cert-manager/cert-manager@v1.18.2\n```\n\n## Managing Core Types\n\nCore Kubernetes API types, such as `Pods`, `Services`, and `Deployments`, are predefined by Kubernetes.\nTo create a controller for these core types without scaffolding the resource,\nuse the Kubernetes group name described in the following\ntable and specify the version and kind.\n\n| Group                    | K8s API Group            |\n|---------------------------|------------------------------------|\n| admission                 | k8s.io/admission                  |\n| admissionregistration      | k8s.io/admissionregistration      |\n| apps                      | apps                              |\n| auditregistration          | k8s.io/auditregistration          |\n| apiextensions              | k8s.io/apiextensions              |\n| authentication             | k8s.io/authentication             |\n| authorization              | k8s.io/authorization              |\n| autoscaling                | autoscaling                       |\n| batch                     | batch                             |\n| certificates               | k8s.io/certificates               |\n| coordination               | k8s.io/coordination               |\n| core                      | core                              |\n| events                    | k8s.io/events                     |\n| extensions                | extensions                        |\n| imagepolicy               | k8s.io/imagepolicy                |\n| networking                | k8s.io/networking                 |\n| node                      | k8s.io/node                       |\n| metrics                   | k8s.io/metrics                    |\n| policy                    | policy                            |\n| rbac.authorization        | k8s.io/rbac.authorization         |\n| scheduling                | k8s.io/scheduling                 |\n| setting                   | k8s.io/setting                    |\n| storage                   | k8s.io/storage                    |\n\nThe command to create a controller to manage `Pods` looks like this:\n\n```shell\nkubebuilder create api --group core --version v1 --kind Pod --controller=true --resource=false\n```\n\nFor instance, to create a controller to manage Deployment the command would be like:\n\n```sh\ncreate api --group apps --version v1 --kind Deployment --controller=true --resource=false\n```\n\nSee the RBAC [markers][markers-rbac] generated for this:\n\n```go\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update\n```\n\nAlso, the RBAC for the above [markers][markers-rbac]:\n\n```yaml\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - apps\n  resources:\n  - deployments/status\n  verbs:\n  - get\n  - patch\n  - update\n```\n\nThis scaffolds a controller for the Core type `corev1.Pod` but skips creating new resource\ndefinitions since the type is already defined in the Kubernetes API.\n\n### Creating a Webhook to Manage a Core Type\n\nYou will run the command with the Core Type data, just as you would for controllers.\nSee an example:\n\n```go\nkubebuilder create webhook --group core --version v1 --kind Pod --programmatic-validation\n```\n[markers-rbac]: ./markers/rbac.md"
  },
  {
    "path": "docs/book/src/reference/watching-resources/predicates-with-watch.md",
    "content": "# Using Predicates to Refine Watches\n\nWhen working with controllers, it's often beneficial to use **Predicates** to\nfilter events and control when the reconciliation loop should be triggered.\n\n[Predicates][predicates-doc] allow you to define conditions based on events (such as create, update, or delete)\nand resource fields (such as labels, annotations, or status fields). By using **[Predicates][predicates-doc]**,\nyou can refine your controller’s behavior to respond only to specific changes in the resources\nit watches.\n\nThis can be especially useful when you want to refine which\nchanges in resources should trigger a reconciliation. By using predicates,\nyou avoid unnecessary reconciliations and can ensure that the\ncontroller only reacts to relevant changes.\n\n## When to Use Predicates\n\n**Predicates are useful when:**\n\n- You want to ignore certain changes, such as updates that don't impact the fields your controller is concerned with.\n- You want to trigger reconciliation only for resources with specific labels or annotations.\n- You want to watch external resources and react only to specific changes.\n\n## Example: Using Predicates to Filter Update Events\n\nLet’s say that we only want our **`BackupBusybox`** controller to reconcile\nwhen certain fields of the **`Busybox`** resource change, for example, when\nthe `spec.size` field changes, but we want to ignore all other changes (such as status updates).\n\n### Defining a Predicate\n\nIn the following example, we define a predicate that only\nallows reconciliation when there’s a meaningful update\nto the **`Busybox`** resource:\n\n```go\nimport (\n    \"sigs.k8s.io/controller-runtime/pkg/predicate\"\n    \"sigs.k8s.io/controller-runtime/pkg/event\"\n)\n\n// Predicate to trigger reconciliation only on size changes in the Busybox spec\nupdatePred := predicate.Funcs{\n    // Only allow updates when the spec.size of the Busybox resource changes\n    UpdateFunc: func(e event.UpdateEvent) bool {\n        oldObj := e.ObjectOld.(*examplecomv1alpha1.Busybox)\n        newObj := e.ObjectNew.(*examplecomv1alpha1.Busybox)\n\n        // Trigger reconciliation only if the spec.size field has changed\n        return oldObj.Spec.Size != newObj.Spec.Size\n    },\n\n    // Allow create events\n    CreateFunc: func(e event.CreateEvent) bool {\n        return true\n    },\n\n    // Allow delete events\n    DeleteFunc: func(e event.DeleteEvent) bool {\n        return true\n    },\n\n    // Allow generic events (e.g., external triggers)\n    GenericFunc: func(e event.GenericEvent) bool {\n        return true\n    },\n}\n```\n\n### Explanation\n\nIn this example:\n- The **`UpdateFunc`** returns `true` only if the **`spec.size`** field has changed between the old and new objects, meaning that all other changes in the `spec`, like annotations or other fields, will be ignored.\n- **`CreateFunc`**, **`DeleteFunc`**, and **`GenericFunc`** return `true`, meaning that create, delete, and generic events are still processed, allowing reconciliation to happen for these event types.\n\nThis ensures that the controller reconciles only when the specific field **`spec.size`** is modified, while ignoring any other modifications in the `spec` that are irrelevant to your logic.\n\n### Example: Using Predicates in `Watches`\n\nNow, we apply this predicate in the **`Watches()`** method of\nthe **`BackupBusyboxReconciler`** to trigger reconciliation only for relevant events:\n\n```go\n// SetupWithManager sets up the controller with the Manager.\n// The controller will watch both the BackupBusybox primary resource and the Busybox resource, using predicates.\nfunc (r *BackupBusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n    return ctrl.NewControllerManagedBy(mgr).\n        For(&examplecomv1alpha1.BackupBusybox{}).  // Watch the primary resource (BackupBusybox)\n        Watches(\n            &examplecomv1alpha1.Busybox{},  // Watch the Busybox CR\n            handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {\n                return []reconcile.Request{\n                    {\n                        NamespacedName: types.NamespacedName{\n                            Name:      \"backupbusybox\",  // Reconcile the associated BackupBusybox resource\n                            Namespace: obj.GetNamespace(),  // Use the namespace of the changed Busybox\n                        },\n                    },\n                }\n            }),\n            builder.WithPredicates(updatePred),  // Apply the predicate\n        ).  // Trigger reconciliation when the Busybox resource changes (if it meets predicate conditions)\n        Complete(r)\n}\n```\n\n### Explanation\n\n- **[`builder.WithPredicates(updatePred)`][predicates-doc]**: This method applies the predicate, ensuring that reconciliation only occurs\nwhen the **`spec.size`** field in **`Busybox`** changes.\n- **Other Events**: The controller will still trigger reconciliation on `Create`, `Delete`, and `Generic` events.\n\n[predicates-doc]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/source#WithPredicates"
  },
  {
    "path": "docs/book/src/reference/watching-resources/secondary-owned-resources.md",
    "content": "# Watching Secondary Resources `Owned` by the Controller\n\nIn Kubernetes controllers, it’s common to manage both **Primary Resources**\nand **Secondary Resources**. A **Primary Resource** is the main resource\nthat the controller is responsible for, while **Secondary Resources**\nare created and managed by the controller to support the **Primary Resource**.\n\nIn this section, we will explain how to manage **Secondary Resources**\nwhich are `Owned` by the controller. This example shows how to:\n\n- Set the [Owner Reference][cr-owner-ref-doc] between the primary resource (`Busybox`) and the secondary resource (`Deployment`) to ensure proper lifecycle management.\n- Configure the controller to `Watch` the secondary resource using `Owns()` in `SetupWithManager()`. See that `Deployment` is owned by the `Busybox` controller because\nit will be created and managed by it.\n\n## Setting the Owner Reference\n\nTo link the lifecycle of the secondary resource (`Deployment`)\nto the primary resource (`Busybox`), we need to set\nan [Owner Reference][cr-owner-ref-doc] on the secondary resource.\nThis ensures that Kubernetes automatically handles cascading deletions:\nif the primary resource is deleted, the secondary resource will also be deleted.\n\nController-runtime provides the [controllerutil.SetControllerReference][cr-owner-ref-doc] function, which you can use to set this relationship between the resources.\n\n### Setting the Owner Reference\n\nBelow, we create the `Deployment` and set the Owner reference between the `Busybox` custom resource and the `Deployment` using `controllerutil.SetControllerReference()`.\n\n```go\n// deploymentForBusybox returns a Deployment object for Busybox\nfunc (r *BusyboxReconciler) deploymentForBusybox(busybox *examplecomv1alpha1.Busybox) *appsv1.Deployment {\n    replicas := busybox.Spec.Size\n\n    dep := &appsv1.Deployment{\n        ObjectMeta: metav1.ObjectMeta{\n            Name:      busybox.Name,\n            Namespace: busybox.Namespace,\n        },\n        Spec: appsv1.DeploymentSpec{\n            Replicas: &replicas,\n            Selector: &metav1.LabelSelector{\n                MatchLabels: map[string]string{\"app\": busybox.Name},\n            },\n            Template: metav1.PodTemplateSpec{\n                ObjectMeta: metav1.ObjectMeta{\n                    Labels: map[string]string{\"app\": busybox.Name},\n                },\n                Spec: corev1.PodSpec{\n                    Containers: []corev1.Container{\n                        {\n                            Name:  \"busybox\",\n                            Image: \"busybox:latest\",\n                        },\n                    },\n                },\n            },\n        },\n    }\n\n    // Set the ownerRef for the Deployment, ensuring that the Deployment\n    // will be deleted when the Busybox CR is deleted.\n    controllerutil.SetControllerReference(busybox, dep, r.Scheme)\n    return dep\n}\n```\n\n### Explanation\n\nBy setting the `OwnerReference`, if the `Busybox` resource is deleted, Kubernetes will automatically delete\nthe `Deployment` as well. This also allows the controller to watch for changes in the `Deployment`\nand ensure that the desired state (such as the number of replicas) is maintained.\n\nFor example, if someone modifies the `Deployment` to change the replica count to 3,\nwhile the `Busybox` CR defines the desired state as 1 replica,\nthe controller will reconcile this and ensure the `Deployment`\nis scaled back to 1 replica.\n\n**Reconcile Function Example**\n\n```go\n// Reconcile handles the main reconciliation loop for Busybox and the Deployment\nfunc (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n    log := logf.FromContext(ctx)\n\n    // Fetch the Busybox instance\n    busybox := &examplecomv1alpha1.Busybox{}\n    if err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n        if apierrors.IsNotFound(err) {\n            log.Info(\"Busybox resource not found. Ignoring since it must be deleted\")\n            return ctrl.Result{}, nil\n        }\n        log.Error(err, \"Failed to get Busybox\")\n        return ctrl.Result{}, err\n    }\n\n    // Check if the Deployment already exists, if not create a new one\n    found := &appsv1.Deployment{}\n    err := r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found)\n    if err != nil && apierrors.IsNotFound(err) {\n        // Define a new Deployment\n        dep := r.deploymentForBusybox(busybox)\n        log.Info(\"Creating a new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n        if err := r.Create(ctx, dep); err != nil {\n            log.Error(err, \"Failed to create new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n            return ctrl.Result{}, err\n        }\n        // Requeue the request to ensure the Deployment is created\n        return ctrl.Result{RequeueAfter: time.Minute}, nil\n    } else if err != nil {\n        log.Error(err, \"Failed to get Deployment\")\n        return ctrl.Result{}, err\n    }\n\n    // Ensure the Deployment size matches the desired state\n    size := busybox.Spec.Size\n    if *found.Spec.Replicas != size {\n        found.Spec.Replicas = &size\n        if err := r.Update(ctx, found); err != nil {\n            log.Error(err, \"Failed to update Deployment size\", \"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n            return ctrl.Result{}, err\n        }\n        // Requeue the request to ensure the correct state is achieved\n        return ctrl.Result{Requeue: true}, nil\n    }\n\n    // Update Busybox status to reflect that the Deployment is available\n    busybox.Status.AvailableReplicas = found.Status.AvailableReplicas\n    if err := r.Status().Update(ctx, busybox); err != nil {\n        log.Error(err, \"Failed to update Busybox status\")\n        return ctrl.Result{}, err\n    }\n\n    return ctrl.Result{}, nil\n}\n```\n\n## Watching Secondary Resources\n\nTo ensure that changes to the secondary resource (such as the `Deployment`) trigger\na reconciliation of the primary resource (`Busybox`), we configure the controller\nto watch both resources.\n\nThe `Owns()` method allows you to specify secondary resources\nthat the controller should monitor. This way, the controller will\nautomatically reconcile the primary resource whenever the secondary\nresource changes (e.g., is updated or deleted).\n\n### Example: Configuring `SetupWithManager` to Watch Secondary Resources\n\n```go\n// SetupWithManager sets up the controller with the Manager.\n// The controller will watch both the Busybox primary resource and the Deployment secondary resource.\nfunc (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n    return ctrl.NewControllerManagedBy(mgr).\n        For(&examplecomv1alpha1.Busybox{}).  // Watch the primary resource\n        Owns(&appsv1.Deployment{}).          // Watch the secondary resource (Deployment)\n        Complete(r)\n}\n```\n\n## Ensuring the Right Permissions\n\nKubebuilder uses [markers][markers] to define RBAC permissions\nrequired by the controller. In order for the controller to\nproperly watch and manage both the primary (`Busybox`) and secondary (`Deployment`)\nresources, it must have the appropriate permissions granted;\ni.e. to `watch`, `get`, `list`, `create`, `update`, and `delete` permissions for those resources.\n\n### Example: RBAC Markers\n\nBefore the `Reconcile` method, we need to define the appropriate RBAC markers.\nThese markers will be used by [controller-gen][controller-gen] to generate the necessary\nroles and permissions when you run `make manifests`.\n\n```go\n// +kubebuilder:rbac:groups=example.com,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n- The first marker gives the controller permission to manage the `Busybox` custom resource (the primary resource).\n- The second marker grants the controller permission to manage `Deployment` resources (the secondary resource).\n\nNote that we are granting permissions to `watch` the resources.\n\n[owner-ref-k8s-docs]: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n[cr-owner-ref-doc]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil#SetOwnerReference\n[controller-gen]: ./../controller-gen.md\n[markers]:./../markers/rbac.md\n"
  },
  {
    "path": "docs/book/src/reference/watching-resources/secondary-resources-not-owned.md",
    "content": "# Watching Secondary Resources that are NOT `Owned`\n\nIn some scenarios, a controller may need to watch and respond to changes in\nresources that it does not `Own`, meaning those resources are created and managed by\nanother controller.\n\nThe following examples demonstrate how a controller can monitor and reconcile resources\nthat it doesn’t directly manage. This applies to any resource not `Owned` by the controller,\nincluding **Core Types** or **Custom Resources** managed by other controllers or projects\nand reconciled in separate processes.\n\nFor instance, consider two custom resources—`Busybox` and `BackupBusybox`.\nIf changes to `Busybox` should trigger reconciliation in the `BackupBusybox` controller, we\ncan configure the `BackupBusybox` controller to watch for updates in `Busybox`.\n\n### Example: Watching a Non-Owned Busybox Resource to Reconcile BackupBusybox\n\nConsider a controller that manages a custom resource `BackupBusybox`\nbut also needs to monitor changes to `Busybox` resources across the cluster.\nWe only want to trigger reconciliation when `Busybox` instances have the Backup\nfeature enabled.\n\n- **Why Watch Secondary Resources?**\n    - The `BackupBusybox` controller is not responsible for creating or owning `Busybox`\n    resources, but changes in these resources (such as updates or deletions) directly affect the primary\n    resource (`BackupBusybox`).\n    - By watching `Busybox` instances with a specific label, the controller ensures that the necessary\n    actions (e.g., backups) are triggered only for the relevant resources.\n\n### Configuration Example\n\nHere’s how to configure the `BackupBusyboxReconciler` to watch changes in the\n`Busybox` resource and trigger reconciliation for `BackupBusybox`:\n\n```go\n// SetupWithManager sets up the controller with the Manager.\n// The controller will watch both the BackupBusybox primary resource and the Busybox resource.\nfunc (r *BackupBusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n    return ctrl.NewControllerManagedBy(mgr).\n        For(&examplecomv1alpha1.BackupBusybox{}).  // Watch the primary resource (BackupBusybox)\n        Watches(\n            &examplecomv1alpha1.Busybox{},  // Watch the Busybox CR\n            handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {\n                // Trigger reconciliation for the BackupBusybox in the same namespace\n                return []reconcile.Request{\n                    {\n                        NamespacedName: types.NamespacedName{\n                            Name:      \"backupbusybox\",  // Reconcile the associated BackupBusybox resource\n                            Namespace: obj.GetNamespace(),  // Use the namespace of the changed Busybox\n                        },\n                    },\n                }\n            }),\n        ).  // Trigger reconciliation when the Busybox resource changes\n        Complete(r)\n}\n```\n\nHere’s how we can configure the controller to filter and watch\nfor changes to only those `Busybox` resources that have the specific label:\n\n```go\n// SetupWithManager sets up the controller with the Manager.\n// The controller will watch both the BackupBusybox primary resource and the Busybox resource, filtering by a label.\nfunc (r *BackupBusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n    return ctrl.NewControllerManagedBy(mgr).\n        For(&examplecomv1alpha1.BackupBusybox{}).  // Watch the primary resource (BackupBusybox)\n        Watches(\n            &examplecomv1alpha1.Busybox{},  // Watch the Busybox CR\n            handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {\n                // Check if the Busybox resource has the label 'backup-needed: \"true\"'\n                if val, ok := obj.GetLabels()[\"backup-enable\"]; ok && val == \"true\" {\n                    // If the label is present and set to \"true\", trigger reconciliation for BackupBusybox\n                    return []reconcile.Request{\n                        {\n                            NamespacedName: types.NamespacedName{\n                                Name:      \"backupbusybox\",  // Reconcile the associated BackupBusybox resource\n                                Namespace: obj.GetNamespace(),  // Use the namespace of the changed Busybox\n                            },\n                        },\n                    }\n                }\n                // If the label is not present or doesn't match, don't trigger reconciliation\n                return []reconcile.Request{}\n            }),\n        ).  // Trigger reconciliation when the labeled Busybox resource changes\n        Complete(r)\n}\n```\n"
  },
  {
    "path": "docs/book/src/reference/watching-resources.md",
    "content": "# Watching Resources\n\nWhen extending the Kubernetes API, we aim to ensure that our solutions behave consistently with Kubernetes itself.\nFor example, consider a `Deployment` resource, which is managed by a controller. This controller is responsible\nfor responding to changes in the cluster—such as when a `Deployment` is created, updated, or deleted—by triggering\nreconciliation to ensure the resource’s state matches the desired state.\n\nSimilarly, when developing our controllers, we want to watch for relevant changes in resources that are crucial\nto our solution. These changes—whether creations, updates, or deletions—should trigger the reconciliation\nloop to take appropriate actions and maintain consistency across the cluster.\n\nThe [controller-runtime][controller-runtime] library provides several ways to watch and manage resources.\n\n## Primary Resources\n\nThe **Primary Resource** is the resource that your controller is responsible\nfor managing. For example, if you create a custom resource definition (CRD) for `MyApp`,\nthe corresponding controller is responsible for managing instances of `MyApp`.\n\nIn this case, `MyApp` is the **Primary Resource** for that controller, and your controller’s\nreconciliation loop focuses on ensuring the desired state of these primary resources is maintained.\n\nWhen you create a new API using Kubebuilder, the following default code is scaffolded,\nensuring that the controller watches all relevant events—such as creations, updates, and\ndeletions—for (`For()`) the new API.\n\nThis setup guarantees that the reconciliation loop is triggered whenever an instance\nof the API is created, updated, or deleted:\n\n```go\n// Watches the primary resource (e.g., MyApp) for create, update, delete events\nif err := ctrl.NewControllerManagedBy(mgr).\n   For(&<YourAPISpec>{}). <-- See there that the Controller is For this API\n   Complete(r); err != nil {\n   return err\n}\n```\n\n## Secondary Resources\n\nYour controller will likely also need to manage **Secondary Resources**,\nwhich are the resources required on the cluster to support the **Primary Resource**.\n\nChanges to these **Secondary Resources** can directly impact the **Primary Resource**,\nso the controller must watch and reconcile these resources accordingly.\n\n### Which are Owned by the Controller\n\nThese **Secondary Resources**, such as `Services`, `ConfigMaps`, or `Deployments`,\nwhen `Owned` by the controllers, are created and managed by the specific controller\nand are tied to the **Primary Resource** via [OwnerReferences][owner-ref-k8s-docs].\n\nFor example, if we have a controller to manage our CR(s) of the Kind `MyApp`\non the cluster, which represents our application solution, all resources required\nto ensure that `MyApp` is up and running with the desired number of instances\nwill be **Secondary Resources**. The code responsible for creating, deleting,\nand updating these resources will be part of the `MyApp` Controller.\nWe would add the appropriate [OwnerReferences][owner-ref-k8s-docs]\nusing the [controllerutil.SetControllerReference][cr-owner-ref-doc]\nfunction to indicate that these resources are owned by the same controller\nresponsible for managing `MyApp` instances, which will be reconciled by the `MyAppReconciler`.\n\nAdditionally, if the **Primary Resource** is deleted, Kubernetes' garbage collection mechanism\nensures that all associated **Secondary Resources** are automatically deleted in a\ncascading manner.\n\n### Which are NOT `Owned` by the Controller\n\nNote that **Secondary Resources** can either be APIs/CRDs defined in your project or in other projects that are\nrelevant to the **Primary Resources**, but which the specific controller is not responsible for creating or managing.\n\nFor example, if we have a CRD that represents a backup solution (i.e. `MyBackup`) for our `MyApp`,\nit might need to watch changes in the `MyApp` resource to trigger reconciliation in `MyBackup`\nto ensure the desired state. Similarly, `MyApp`'s behavior might also be impacted by\nCRDs/APIs defined in other projects.\n\nIn both scenarios, these resources are treated as **Secondary Resources**, even if they are not `Owned`\n(i.e., not created or managed) by the `MyAppController`.\n\nIn Kubebuilder, resources that are not defined in the project itself and are not\na **Core Type** (those not defined in the Kubernetes API) are called **External Types**.\n\nAn **External Type** refers to a resource that is not defined in your\nproject but one that you need to watch and respond to.\nFor example, if **Operator A** manages a `MyApp` CRD for application deployment,\nand **Operator B** handles backups, **Operator B** can watch the `MyApp` CRD as an external type\nto trigger backup operations based on changes in `MyApp`.\n\nIn this scenario, **Operator B** could define a `BackupConfig` CRD that relies on the state of `MyApp`.\nBy treating `MyApp` as a **Secondary Resource**, **Operator B** can watch and reconcile changes in **Operator A**'s `MyApp`,\nensuring that backup processes are initiated whenever `MyApp` is updated or scaled.\n\n## General Concept of Watching Resources\n\nWhether a resource is defined within your project or comes from an external project, the concept of **Primary**\nand **Secondary Resources** remains the same:\n- The **Primary Resource** is the resource the controller is primarily responsible for managing.\n- **Secondary Resources** are those that are required to ensure the primary resource works as desired.\n\nTherefore, regardless of whether the resource was defined by your project or by another project,\nyour controller can watch, reconcile, and manage changes to these resources as needed.\n\n## Why does watching the secondary resources matter?\n\nWhen building a Kubernetes controller, it’s crucial to not only focus\non **Primary Resources** but also to monitor **Secondary Resources**.\nFailing to track these resources can lead to inconsistencies in your\ncontroller's behavior and the overall cluster state.\n\nSecondary resources may not be directly managed by your controller,\nbut changes to these resources can still significantly\nimpact the primary resource and your controller's functionality.\nHere are the key reasons why it's important to watch them:\n\n- **Ensuring Consistency**:\n    - Secondary resources (e.g., child objects or external dependencies) may diverge from their desired state.\n    For instance, a secondary resource may be modified or deleted, causing the system to fall out of sync.\n    - Watching secondary resources ensures that any changes are detected immediately, allowing the controller to\n    reconcile and restore the desired state.\n\n- **Avoiding Random Self-Healing**:\n    - Without watching secondary resources, the controller may \"heal\" itself only upon restart or when specific events\n    are triggered. This can cause unpredictable or delayed reactions to issues.\n    - Monitoring secondary resources ensures that inconsistencies are addressed promptly, rather than waiting for a\n    controller restart or external event to trigger reconciliation.\n\n- **Effective Lifecycle Management**:\n    - Secondary resources might not be owned by the controller directly, but their state still impacts the behavior\n    of primary resources. Without watching these, you risk leaving orphaned or outdated resources.\n    - Watching non-owned secondary resources lets the controller respond to lifecycle events (create, update, delete)\n    that might affect the primary resource, ensuring consistent behavior across the system.\n\nSee [Watching Secondary Resources That Are Not Owned](./watching-resources/secondary-resources-not-owned.md#configuration-example) for an example.\n\n## Why not use `RequeueAfter X` for all scenarios instead of watching resources?\n\nKubernetes controllers are fundamentally **event-driven**. When creating a controller,\nthe **Reconciliation Loop** is typically triggered by **events** such as `create`, `update`, or\n`delete` actions on resources. This event-driven approach is more efficient and responsive\ncompared to constantly requeuing or polling resources using `RequeueAfter`. This ensures that\nthe system only takes action when necessary, maintaining both performance and efficiency.\n\nIn many cases, **watching resources** is the preferred approach for ensuring Kubernetes resources\nremain in the desired state. It is more efficient, responsive, and aligns with Kubernetes' event-driven architecture.\nHowever, there are scenarios where `RequeueAfter` is appropriate and necessary, particularly for managing external\nsystems that do not emit events or for handling resources that take time to converge, such as long-running processes.\nRelying solely on `RequeueAfter` for all scenarios can lead to unnecessary overhead and\ndelayed reactions. Therefore, it is essential to prioritize **event-driven reconciliation** by configuring\nyour controller to **watch resources** whenever possible, and reserving `RequeueAfter` for situations\nwhere periodic checks are required.\n\n### When `RequeueAfter X` is Useful\n\nWhile `RequeueAfter` is not the primary method for triggering reconciliations, there are specific cases where it is\nnecessary, such as:\n\n- **Observing External Systems**: When working with external resources that do not generate events\n(e.g., external databases or third-party services), `RequeueAfter` allows the\ncontroller to periodically check the status of these resources.\n- **Time-Based Operations**: Some tasks, such as rotating secrets or\nrenewing certificates, must happen at specific intervals. `RequeueAfter` ensures these operations\nare performed on schedule, even when no other changes occur.\n- **Handling Errors or Delays**: When managing resources that encounter errors or require time to self-heal,\n`RequeueAfter` ensures the controller waits for a specified duration before checking the resource’s status again,\navoiding constant reconciliation attempts.\n\n## Usage of Predicates\n\nFor more complex use cases, [Predicates][cr-predicates] can be used to fine-tune\nwhen your controller should trigger reconciliation. Predicates allow you to filter\nevents based on specific conditions, such as changes to particular fields, labels, or annotations,\nensuring that your controller only responds to relevant events and operates efficiently.\n\n[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime\n[owner-ref-k8s-docs]: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n[cr-predicates]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/predicate\n[secondary-resources-doc]: watching-resources/secondary-owned-resources\n[predicates-with-external-type-doc]: watching-resources/predicates-with-watch\n[cr-owner-ref-doc]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/controller/controllerutil#SetOwnerReference\n"
  },
  {
    "path": "docs/book/src/reference/webhook-bootstrap-problem.md",
    "content": "# Webhook Bootstrap Problem\n\n## The Problem\n\nWhen you create a webhook for a **core Kubernetes type** (Pod, Deployment, Job, etc.), the webhook can block its own controller Pod from starting, causing a deployment deadlock.\n\n**Example command:**\n```bash\nkubebuilder create webhook --group core --version v1 --kind Pod --programmatic-validation\n```\n\n**Example scenario:**\n\n1. You create a validating webhook for Pods\n2. You deploy your controller (which runs in a Pod)\n3. Kubernetes tries to create your controller Pod\n4. Your webhook intercepts this Pod creation\n5. The webhook server isn't ready yet (it's inside the Pod being created)\n6. The Pod creation hangs waiting for webhook validation\n7. The webhook never starts because the Pod is blocked\n\n**Result:** Deadlock. Your deployment fails.\n\n## When Does This Occur?\n\n### Core Kubernetes Types\n\nThe bootstrap problem occurs when creating webhooks for built-in Kubernetes resources:\n\n- `core` group: Pod, Service, Namespace, ConfigMap, Secret\n- `apps` group: Deployment, StatefulSet, DaemonSet, ReplicaSet\n- `batch` group: Job, CronJob\n- Other built-in types\n\n**Why?** Your webhook validates the same type of resource that your controller deployment creates (typically Pods or Deployments).\n\n### Custom CRDs\n\nThe bootstrap problem **does not occur** with custom resource webhooks:\n\n- Your webhook validates `MyResource` objects\n- Your controller runs as a Pod\n- Pods and MyResources are different types\n- No circular dependency\n\n## How to Fix\n\nConfigure your webhook to **skip validating its own resources** using either `namespaceSelector` or `objectSelector`.\n\n### Option 1: namespaceSelector (Recommended)\n\nExclude the entire namespace where your webhook runs.\n\n**Step 1:** Add label to the Namespace in `config/manager/manager.yaml`:\n\n```yaml\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: my-project\n    app.kubernetes.io/managed-by: kustomize\n    webhook-excluded: \"true\"\n  name: system\n```\n\n**Step 2:** Create patch file `config/webhook/namespaceselector_patch.yaml`:\n\n```yaml\n# For mutating webhooks (--defaulting)\n- op: add\n  path: /webhooks/0/namespaceSelector\n  value:\n    matchExpressions:\n    - key: webhook-excluded\n      operator: DoesNotExist\n```\n\nFor validating webhooks (`--programmatic-validation`), create a similar patch targeting `ValidatingWebhookConfiguration`.\n\n**Step 3:** Add patch to `config/webhook/kustomization.yaml`:\n\n```yaml\nresources:\n- manifests.yaml\n- service.yaml\n\npatches:\n- path: namespaceselector_patch.yaml\n  target:\n    group: admissionregistration.k8s.io\n    version: v1\n    kind: MutatingWebhookConfiguration\n    name: mutating-webhook-configuration\n```\n\n**Step 4:** Deploy:\n\n```bash\nmake deploy IMG=<your-image>\n```\n\n### Option 2: objectSelector\n\nExclude specific labeled Pods from webhook validation.\n\n**Step 1:** Add label to Pods in `config/manager/manager.yaml`:\n\n```yaml\nspec:\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: my-project\n        webhook-excluded: \"true\"\n```\n\n**Step 2-4:** Same as Option 1, but use `objectSelector` instead of `namespaceSelector` in the patch file.\n\n### Multiple Webhooks\n\nIf you created webhooks for multiple core types (e.g., Pod and Deployment), you'll have multiple webhook entries.\n\n**Check webhook count:**\n\n```bash\nmake manifests\ngrep \"  name: m\" config/webhook/manifests.yaml  # Count mutating webhooks\ngrep \"  name: v\" config/webhook/manifests.yaml  # Count validating webhooks\n```\n\n**Example output:**\n\n```\n  name: mpod-v1.kb.io         # Index 0\n  name: mdeployment-v1.kb.io  # Index 1\n```\n\n**Add patches for all indices** in your patch file:\n\n```yaml\n- op: add\n  path: /webhooks/0/namespaceSelector\n  value:\n    matchExpressions:\n    - key: webhook-excluded\n      operator: DoesNotExist\n- op: add\n  path: /webhooks/1/namespaceSelector\n  value:\n    matchExpressions:\n    - key: webhook-excluded\n      operator: DoesNotExist\n```\n\n### Mixed Webhooks (CRD + Core Types)\n\nIf you have both custom CRD webhooks and core type webhooks:\n\n- CRD webhooks appear first in the configuration\n- Core type webhooks appear after\n- Count **all** webhooks and add patches for the indices of your core type webhooks\n\n**Example:** If you have 1 CRD webhook (index 0) and 1 core type webhook (index 1), your patch should target index 1:\n\n```yaml\n- op: add\n  path: /webhooks/1/namespaceSelector\n  value:\n    matchExpressions:\n    - key: webhook-excluded\n      operator: DoesNotExist\n```\n\n## Choosing Between namespaceSelector and objectSelector\n\n| Feature | namespaceSelector | objectSelector |\n|---------|-------------------|----------------|\n| Excludes | Entire namespace | Specific pods |\n| Scope | Broad | Fine-grained |\n| Best for | Dedicated webhook namespace | Shared namespace |\n| Complexity | Simple | More targeted |\n\n**Recommendation:** Use `namespaceSelector` unless you need fine-grained control.\n\n## References\n\n- [Kubernetes Admission Webhook Best Practices](https://kubernetes.io/docs/concepts/cluster-administration/admission-webhooks-good-practices/)\n- [namespaceSelector API Reference](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector)\n- [objectSelector API Reference](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector)\n"
  },
  {
    "path": "docs/book/src/reference/webhook-overview.md",
    "content": "# Webhook\n\nWebhooks are requests for information sent in a blocking fashion. A web\napplication implementing webhooks will send a HTTP request to other applications\nwhen a certain event happens.\n\nIn the kubernetes world, there are 3 kinds of webhooks:\n[admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks),\n[authorization webhook](https://kubernetes.io/docs/reference/access-authn-authz/webhook/) and\n[CRD conversion webhook](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion).\n\nIn [controller-runtime](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/webhook?tab=doc)\nlibraries, we support admission webhooks and CRD conversion webhooks.\n\nKubernetes supports these dynamic admission webhooks as of version 1.9 (when the\nfeature entered beta).\n\nKubernetes supports the conversion webhooks as of version 1.15 (when the\nfeature entered beta).\n\n"
  },
  {
    "path": "docs/book/src/versions_compatibility_supportability.md",
    "content": "# Versions Compatibility and Supportability\n\nProjects created by Kubebuilder contain a `Makefile` that installs tools at versions defined during project creation.\nThe main tools included are:\n\n- [kustomize](https://github.com/kubernetes-sigs/kustomize)\n- [controller-gen](https://github.com/kubernetes-sigs/controller-tools)\n- [setup-envtest](https://github.com/kubernetes-sigs/controller-runtime/tree/main/tools/setup-envtest)\n\nAdditionally, these projects include a `go.mod` file specifying dependency versions.\nKubebuilder relies on [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) and its Go and Kubernetes dependencies.\nTherefore, the versions defined in the `Makefile` and `go.mod` files are the ones that have been tested, supported, and recommended.\n\nEach minor version of Kubebuilder is tested with a specific minor version of client-go.\nWhile a Kubebuilder minor version *may* be compatible with other client-go minor versions,\nor other tools this compatibility is not guaranteed, supported, or tested.\n\nThe minimum Go version required by Kubebuilder is determined by the highest minimum\nGo version required by its dependencies. This is usually aligned with the minimum\nGo version required by the corresponding `k8s.io/*` dependencies.\n\nCompatible `k8s.io/*` versions, client-go versions, and minimum Go versions can be found in the `go.mod`\nfile scaffolded for each project for each [tag release](https://github.com/kubernetes-sigs/kubebuilder/tags).\n\n**Example:** For the `4.1.1` release, the minimum Go version compatibility is `1.22`.\nYou can refer to the samples in the testdata directory of the tag released [v4.1.1](https://github.com/kubernetes-sigs/kubebuilder/tree/v4.1.1/testdata),\nsuch as the [go.mod](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.1.1/testdata/project-v4/go.mod#L3) file for `project-v4`. You can also check the tools versions supported and\ntested for this release by examining the [Makefile](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.1.1/testdata/project-v4/Makefile#L160-L165).\n\n## Operating Systems Supported\n\nCurrently, Kubebuilder officially supports macOS and Linux platforms. If you are using a Windows OS, we recommend you read the instructions in [here](https://github.com/kubernetes-sigs/kubebuilder/blob/master/docs/windows.md).\n\nContributions towards supporting Windows are not planned.\n\n<aside class=\"warning\">\n    <h3>Project customizations</h3>\n\nAfter using the CLI to create your project, you are free to customize how\nyou see fit. Bear in mind, that it is not recommended to deviate from\nthe proposed layout unless you know what you are doing.\n\nFor example, you should refrain from moving the scaffolded files,\ndoing so will make it difficult in upgrading your project in the future.\nYou may also lose the ability to use some of the CLI features and helpers.\nFor further information on the project layout, see the doc [What's in a basic project?][basic-project-doc]\n\n</aside>\n\n[basic-project-doc]: ./cronjob-tutorial/basic-project.md\n"
  },
  {
    "path": "docs/book/theme/css/custom.css",
    "content": "/* Adds a thin border to the sidebar (aesthetics) */\n#mdbook-sidebar {\n    border-right: 1px solid var(--theme-popup-border);\n}\n\n/* Sets the size of the logo on the menu bar */\n.large-logo-img {\n    height: 50px;\n}\n\n/* Centers the logo on the menu bar */\n.menu-title {\n    display: flex;\n    align-items: center; /* vertical centering */\n    justify-content: center; /* horizontal centering */\n}\n\n/* Fixes first header sliding under the menu bar when selected */\n.content {\n    overflow-y: clip;\n}\n\n/* Fixes contrast in codeblocks */\npre > .hljs {\n    border: 1px solid var(--theme-popup-border);\n    border-radius: 6px;\n}\n\n/* Fixes scrollbar background color */\nhtml {\n    scrollbar-color: var(--scrollbar) transparent;\n}\n\n/* Fixes links formatting */\n.content a {\n    text-decoration: solid underline var(--links);\n}\n\n/* Hides excessive theme options */\n#mdbook-theme-default_theme,\n#mdbook-theme-rust,\n#mdbook-theme-coal,\n#mdbook-theme-ayu {\n    display: none;\n}\n\n/* custom light theme */\n.light {\n    --sidebar-bg: #eef5ff;\n    --sidebar-fg: #334155;\n    --sidebar-non-existant: #7b8da8;\n    --sidebar-spacer: #c9d3e0;\n}\n"
  },
  {
    "path": "docs/book/theme/css/markers.css",
    "content": "/* From here on out is custom stuff */\n\n/* marker docs styles */\n\n/* NB(directxman12): The general gist of this is that we use semantic markup\n * for the actual HTML as much as possible, and then use CSS to look pretty and\n * extract the actual relevant information.  Theoretically, this'll let us do\n * stuff like transform the information for different screen widths. */\n\n/* Removes the counter from marker definitions */\ndd:has(+ dd)::before,\ndd + dd::before {\n    content: normal;\n}\n\n/* the marker */\n.marker {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    margin-bottom: 0.25em;\n}\n\n.marker > dt.name {\n    font-weight: bold;\n}\n\n/* the target blob */\n.marker::before {\n    content: \"on \" attr(data-target);\n    padding: 1px 6px;\n    border-radius: 20%;\n    background: var(--quote-bg);\n    margin-left: 0.5em;\n    font-weight: normal;\n    opacity: 0.75;\n    font-size: 0.75em;\n    order: 2; /* hack around the ::before's positioning to get it after the line */\n}\n\n/* deprecated markers */\n.marker.deprecated[data-target] {\n    /* use attribute marker for specificity */\n    order: 4;\n    opacity: 0.65;\n}\n\n.marker.deprecated::before {\n    content: \"deprecated (on \" attr(data-target) \")\";\n    color: red;\n}\n.marker.deprecated:not([data-deprecated=\"\"])::before {\n    content: \"use \" attr(data-deprecated) \" (on \" attr(data-target) \")\";\n    color: red;\n}\n\n/* the summary arguments (hidden in non-summary view) */\n.marker dd.args {\n    margin-left: 0;\n    font-family: mono;\n    order: 1; /* hack around the ::before's positioning to get it after the line */\n}\n.marker dl.args.summary {\n    display: inline-block;\n    margin-bottom: 0;\n    margin-top: 0;\n}\n/* TODO(directxman12): optional */\n.marker dl.args.summary dt {\n    display: inline-block;\n    font-style: inherit;\n}\n.marker dl.args.summary dt:first-child::before {\n    content: \":\";\n}\n.marker dl.args.summary dt::before {\n    content: \",\";\n}\n/* hide in non-summary view */\n.marker dd.args {\n    display: none;\n}\n\n/* the description */\n.marker dd.description {\n    order: 3; /* hack around the ::before's positioning to get it after the line */\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n}\n\n/* all arguments */\n.marker dl.args dt.argument::after {\n    content: \"=\";\n}\n.marker dl.args dd.type {\n    font-style: italic;\n}\n.marker .argument {\n    display: inline-block;\n    margin-left: 0;\n}\n.marker .argument.type {\n    font-size: 0.875em;\n}\n.marker .literal {\n    font-family:\n        \"Source Code Pro\", Consolas, \"Ubuntu Mono\", Menlo, \"DejaVu Sans Mono\",\n        monospace, monospace;\n    font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */\n}\n.marker .argument.type::before {\n    content: \"‹\";\n}\n.marker .argument.type::after {\n    content: \"›\";\n}\n\n/* summary args */\n.marker .args.summary .argument.optional {\n    opacity: 0.75;\n}\n\n/* anonymous marker args */\n.marker.anonymous .description details {\n    order: 1;\n    flex: 1; /* don't cause arg syntax to wrap */\n}\n.marker.anonymous .description .args {\n    order: 0; /* go before the description */\n\n    /* all on a single line */\n    margin-top: 0;\n    margin-bottom: 0;\n    margin-right: 1em;\n}\n.marker.anonymous .description {\n    flex-direction: row;\n}\n.marker .description dl.args:empty {\n    margin-top: 0;\n}\n\n.marker .type .slice::before {\n    content: \"[]\";\n}\n\n/* description args */\n.marker .description dt.argument.optional::before {\n    content: \"opt\";\n    padding: 1px 4px;\n    border-radius: 20%;\n    background: var(--quote-bg);\n    opacity: 0.5;\n    margin-left: -3em;\n    float: left;\n}\n\n/* help text */\n.marker summary.no-details {\n    list-style: none;\n}\n.marker summary.no-details::-webkit-details-marker {\n    display: none;\n}\n\n/* summary view */\n.markers-summarize:checked ~ dl > .marker dd.args {\n    display: inline-block;\n}\n.markers-summarize:checked ~ dl > .marker dd.description dl.args {\n    display: none;\n}\n.markers-summarize:checked ~ dl > .marker dd.description {\n    margin-bottom: 0.25em;\n}\n\ninput.markers-summarize {\n    display: none;\n}\nlabel.markers-summarize::before {\n    margin-right: 0.5em;\n    content: \"\\25bc\";\n    display: inline-block;\n}\ninput.markers-summarize:checked ~ label.markers-summarize::before {\n    content: \"\\25b6\";\n}\n\n/* misc */\n/* marker details should be indented to be in line with the summary,\n * which is indented due to the expando\n */\n.marker details > p {\n    margin-left: 1em;\n}\n\n/* sort by target */\n.marker[data-target=\"package\"] {\n    order: 2;\n}\n.marker[data-target=\"type\"] {\n    order: 1;\n}\n.marker[data-target=\"field\"] {\n    order: 0;\n}\n.markers {\n    display: flex;\n    flex-direction: column;\n}\n\n/* don't add margin between collapsed code elements and pre blocks */\n/* (this just removes all margin around pre, but that's generally fine) */\n.literate pre {\n    margin: 0;\n}\n.literate cite.literate-source + * {\n    /* a bit of margin against the cite element */\n    margin-top: 0.125em;\n}\n\n/* Completely hide low-value collapsed sections (license text, imports) */\ndetails.collapse-hide {\n    display: none;\n}\n\n/* details elements (not markers) */\ndetails.collapse-code > summary {\n    width: 100%;\n    cursor: pointer;\n    display: flex;\n    box-sizing: border-box; /* why isn't this the default? :-/ */\n}\n\ndetails.collapse-code > summary::after {\n    content: \"\\25c0\";\n    float: right;\n    font-size: 0.875em;\n    color: var(--inline-code-color);\n    opacity: 0.8;\n}\n\ndetails.collapse-code[open] > summary::after {\n    content: \"\\25bc\";\n}\n\ndetails.collapse-code > summary pre {\n    flex: 1;\n    box-sizing: border-box; /* why isn't this the default? :-/ */\n    margin: inherit;\n    padding: 0.25em 0.5em;\n}\n\ndetails.collapse-code > summary pre span::after {\n    content: \" (hidden)\";\n    font-size: 80%;\n}\n\ndetails.collapse-code[open] > summary pre span::after {\n    content: \"\";\n}\n\ndetails.collapse-code > summary pre span.collapse-summary::before {\n    content: \"// \";\n}\n\ndetails.collapse-code > summary pre span::before {\n    content: \"// \";\n}\n\n/* make summary into code a bit nicer looking */\ndetails.collapse-code[open] > summary + pre {\n    margin-top: 0;\n}\n\n/* get rid of the ugly blue box that makes the summary->code look bad */\ndetails.collapse-code summary:focus {\n    outline: none;\n    font-weight: bold; /* keep something around for tab users */\n}\n\n/* don't show the default expando */\ndetails.collapse-code > summary {\n    list-style: none;\n}\ndetails.collapse-code > summary::-webkit-details-marker {\n    display: none;\n}\n\n/* diagrams */\n\n.diagrams {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n}\n\n.diagrams > * {\n    margin-left: 1em;\n    margin-right: 1em;\n    font-size: 160%;\n    font-weight: bold;\n}\n\n.diagrams object,\n.diagrams svg {\n    max-width: 100%;\n    max-height: 10em; /* force svg height to behave */\n}\n\n.diagrams path,\n.diagrams polyline,\n.diagrams circle {\n    stroke: var(--fg);\n}\n\n.diagrams path.text {\n    fill: var(--fg);\n    stroke: none;\n}\n\n.diagrams path.text.invert {\n    fill: black;\n    stroke: none;\n}\n\n/* notes */\naside.note {\n    border: 1px solid var(--searchbar-border-color);\n    border-radius: 3px;\n    margin-top: 1em;\n}\n\naside.note > * {\n    margin-left: 1em;\n    margin-right: 1em;\n}\n\n/* note title */\naside.note > h1 {\n    border-bottom: 1px solid var(--searchbar-border-color);\n    margin: 0;\n    padding: 0.5em 1em;\n    font-size: 100%;\n    font-weight: normal;\n    background: var(--quote-bg);\n}\n\n/* warning notes */\naside.note.warning > h1 {\n    background: var(--warning-note-background-color, #fcf8f2);\n}\naside.note.warning > h1::before {\n    /* TODO(directxman12): fill in these colors in theme.\n     * If you're good with colors, feel free to play around with this\n     * in dark mode. */\n    content: \"!\";\n    color: var(--warning-note-color, #f0ad4e);\n    margin-right: 1em;\n    font-size: 100%;\n    vertical-align: middle;\n    font-weight: bold;\n    padding-left: 0.6em;\n    padding-right: 0.6em;\n    border-radius: 50%;\n    border: 2px solid var(--warning-note-color, #f0ad4e);\n}\n\n/* literate source citations */\ncite.literate-source {\n    font-size: 75%;\n    font-family:\n        \"Source Code Pro\", Consolas, \"Ubuntu Mono\", Menlo, \"DejaVu Sans Mono\",\n        monospace, monospace;\n}\ncite.literate-source::before {\n    content: \"$ \";\n    font-weight: bold;\n    font-style: normal;\n}\n\ncite.literate-source > a::before {\n    content: \"vim \";\n    font-style: normal;\n    color: var(--fg);\n}\n\n/* add a bit of extra padding for readability */\n.literate pre code {\n    padding-top: 0.75em;\n    padding-bottom: 0.75em;\n}\n"
  },
  {
    "path": "docs/book/theme/css/version-dropdown.css",
    "content": ".version-dropdown-content {\n    display: none;\n    position: absolute;\n    background-color: #f9f9f9;\n    min-width: 90px;\n    box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);\n    z-index: 1;\n  }\n  \n  .version-dropdown-content a {\n    color: black;\n    padding: 12px 16px;\n    text-decoration: none;\n    display: block;\n  }\n  \n  .version-dropdown-content a:hover {\n    background-color: #f1f1f1;\n  }\n  \n  .version-dropdown:hover .version-dropdown-content {\n    display: block;\n  }"
  },
  {
    "path": "docs/book/theme/index.hbs",
    "content": "<!DOCTYPE HTML>\n<html lang=\"{{ language }}\" class=\"{{ default_theme }} sidebar-visible\" dir=\"{{ text_direction }}\">\n    <head>\n        <!-- Book generated using mdBook -->\n        <meta charset=\"UTF-8\">\n        <title>{{ title }}</title>\n        {{#if is_print }}\n        <meta name=\"robots\" content=\"noindex\">\n        {{/if}}\n        {{#if base_url}}\n        <base href=\"{{ base_url }}\">\n        {{/if}}\n\n\n        <!-- Custom HTML head -->\n        {{> head}}\n\n        <meta name=\"description\" content=\"{{ description }}\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <meta name=\"theme-color\" content=\"#ffffff\">\n\n        {{#if favicon_svg}}\n        <link rel=\"icon\" href=\"{{ resource \"logos/favicon.svg\" }}\">\n        {{/if}}\n        {{#if favicon_png}}\n        <link rel=\"shortcut icon\" href=\"{{ resource \"logos/favicon.png\" }}\">\n        {{/if}}\n        <link rel=\"stylesheet\" href=\"{{ resource \"css/variables.css\" }}\">\n        <link rel=\"stylesheet\" href=\"{{ resource \"css/general.css\" }}\">\n        <link rel=\"stylesheet\" href=\"{{ resource \"css/chrome.css\" }}\">\n        {{#if print_enable}}\n        <link rel=\"stylesheet\" href=\"{{ resource \"css/print.css\" }}\" media=\"print\">\n        {{/if}}\n\n        <!-- Fonts -->\n        <link rel=\"stylesheet\" href=\"{{ resource \"fonts/fonts.css\" }}\">\n\n        <!-- Highlight.js Stylesheets -->\n        <link rel=\"stylesheet\" id=\"mdbook-highlight-css\" href=\"{{ resource \"highlight.css\" }}\">\n        <link rel=\"stylesheet\" id=\"mdbook-tomorrow-night-css\" href=\"{{ resource \"tomorrow-night.css\" }}\">\n        <link rel=\"stylesheet\" id=\"mdbook-ayu-highlight-css\" href=\"{{ resource \"ayu-highlight.css\" }}\">\n\n        <!-- Custom theme stylesheets -->\n        {{#each additional_css}}\n        <link rel=\"stylesheet\" href=\"{{ resource this }}\">\n        {{/each}}\n\n        {{#if mathjax_support}}\n        <!-- MathJax -->\n        <script async src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML\"></script>\n        {{/if}}\n\n        <!-- Provide site root and default themes to javascript -->\n        <script>\n            const path_to_root = \"{{ path_to_root }}\";\n            const default_light_theme = \"{{ default_theme }}\";\n            const default_dark_theme = \"{{ preferred_dark_theme }}\";\n        {{#if search_js}}\n            window.path_to_searchindex_js = \"{{ resource \"searchindex.js\" }}\";\n        {{/if}}\n        </script>\n        <!-- Start loading toc.js asap -->\n        <script src=\"{{ resource \"toc.js\" }}\"></script>\n    </head>\n    <body>\n    <div id=\"mdbook-help-container\">\n        <div id=\"mdbook-help-popup\">\n            <h2 class=\"mdbook-help-title\">Keyboard shortcuts</h2>\n            <div>\n                <p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>\n                {{#if search_enabled}}\n                <p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>\n                {{/if}}\n                <p>Press <kbd>?</kbd> to show this help</p>\n                <p>Press <kbd>Esc</kbd> to hide this help</p>\n            </div>\n        </div>\n    </div>\n    <div id=\"mdbook-body-container\">\n        <!-- Work around some values being stored in localStorage wrapped in quotes -->\n        <script>\n            try {\n                let theme = localStorage.getItem('mdbook-theme');\n                let sidebar = localStorage.getItem('mdbook-sidebar');\n\n                if (theme.startsWith('\"') && theme.endsWith('\"')) {\n                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));\n                }\n\n                if (sidebar.startsWith('\"') && sidebar.endsWith('\"')) {\n                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));\n                }\n            } catch (e) { }\n        </script>\n\n        <!-- Set the theme before any content is loaded, prevents flash -->\n        <script>\n            const default_theme = window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? default_dark_theme : default_light_theme;\n            let theme;\n            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }\n            if (theme === null || theme === undefined) { theme = default_theme; }\n            const html = document.documentElement;\n            html.classList.remove('{{ default_theme }}')\n            html.classList.add(theme);\n            html.classList.add(\"js\");\n        </script>\n\n        <input type=\"checkbox\" id=\"mdbook-sidebar-toggle-anchor\" class=\"hidden\">\n\n        <!-- Hide / unhide sidebar before it is displayed -->\n        <script>\n            let sidebar = null;\n            const sidebar_toggle = document.getElementById(\"mdbook-sidebar-toggle-anchor\");\n            if (document.body.clientWidth >= 1080) {\n                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }\n                sidebar = sidebar || 'visible';\n            } else {\n                sidebar = 'hidden';\n                sidebar_toggle.checked = false;\n            }\n            if (sidebar === 'visible') {\n                sidebar_toggle.checked = true;\n            } else {\n                html.classList.remove('sidebar-visible');\n            }\n        </script>\n\n        <nav id=\"mdbook-sidebar\" class=\"sidebar\" aria-label=\"Table of contents\">\n            <!-- populated by js -->\n            <mdbook-sidebar-scrollbox class=\"sidebar-scrollbox\"></mdbook-sidebar-scrollbox>\n            <noscript>\n                <iframe class=\"sidebar-iframe-outer\" src=\"{{ path_to_root }}toc.html\"></iframe>\n            </noscript>\n            <div id=\"mdbook-sidebar-resize-handle\" class=\"sidebar-resize-handle\">\n                <div class=\"sidebar-resize-indicator\"></div>\n            </div>\n        </nav>\n\n        <div id=\"mdbook-page-wrapper\" class=\"page-wrapper\">\n\n            <div class=\"page\">\n                {{> header}}\n                <div id=\"mdbook-menu-bar-hover-placeholder\"></div>\n                <div id=\"mdbook-menu-bar\" class=\"menu-bar sticky\">\n                    <div class=\"left-buttons\">\n                        <label id=\"mdbook-sidebar-toggle\" class=\"icon-button\" for=\"mdbook-sidebar-toggle-anchor\" title=\"Toggle Table of Contents\" aria-label=\"Toggle Table of Contents\" aria-controls=\"mdbook-sidebar\">\n                            {{fa \"solid\" \"bars\"}}\n                        </label>\n                        <button id=\"mdbook-theme-toggle\" class=\"icon-button\" type=\"button\" title=\"Change theme\" aria-label=\"Change theme\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"mdbook-theme-list\">\n                            {{fa \"solid\" \"paintbrush\"}}\n                        </button>\n                        <ul id=\"mdbook-theme-list\" class=\"theme-popup\" aria-label=\"Themes\" role=\"menu\">\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-default_theme\">Auto</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-light\">Light</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-rust\">Rust</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-coal\">Coal</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-navy\">Navy</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"mdbook-theme-ayu\">Ayu</button></li>\n                        </ul>\n                        {{#if search_enabled}}\n                        <button id=\"mdbook-search-toggle\" class=\"icon-button\" type=\"button\" title=\"Search (`/`)\" aria-label=\"Toggle Searchbar\" aria-expanded=\"false\" aria-keyshortcuts=\"/ s\" aria-controls=\"mdbook-searchbar\">\n                            {{fa \"solid\" \"magnifying-glass\"}}\n                        </button>\n                        {{/if}}\n                    </div>\n\n                    <h1 class=\"menu-title\"><img alt=\"{{ book_title }}\" class=\"large-logo-img\" src=\"{{ resource \"/logos/kb-logo-one-line.svg\" }}\"></h1>\n\n                    <div class=\"right-buttons\">\n                        {{#if print_enable}}\n                        <a href=\"{{ path_to_root }}print.html\" title=\"Print this book\" aria-label=\"Print this book\">\n                            {{fa \"solid\" \"print\" \"print-button\"}}\n                        </a>\n                        {{/if}}\n                        {{#if git_repository_url}}\n                        <a href=\"{{git_repository_url}}\" title=\"Git repository\" aria-label=\"Git repository\">\n                            {{fa git_repository_icon_class git_repository_icon}}\n                        </a>\n                        {{/if}}\n                        {{#if git_repository_edit_url}}\n                        <a href=\"{{git_repository_edit_url}}\" title=\"Suggest an edit\" aria-label=\"Suggest an edit\" rel=\"edit\">\n                            {{fa \"solid\" \"pencil\" \"git-edit-button\"}}\n                        </a>\n                        {{/if}}\n\n                    </div>\n                </div>\n\n                {{#if search_enabled}}\n                <div id=\"mdbook-search-wrapper\" class=\"hidden\">\n                    <form id=\"mdbook-searchbar-outer\" class=\"searchbar-outer\">\n                        <div class=\"search-wrapper\">\n                            <input type=\"search\" id=\"mdbook-searchbar\" name=\"searchbar\" placeholder=\"Search this book ...\" aria-controls=\"mdbook-searchresults-outer\" aria-describedby=\"searchresults-header\">\n                            <div class=\"spinner-wrapper\">\n                                {{fa \"solid\" \"spinner\" \"fa-spin\"}}\n                            </div>\n                        </div>\n                    </form>\n                    <div id=\"mdbook-searchresults-outer\" class=\"searchresults-outer hidden\">\n                        <div id=\"mdbook-searchresults-header\" class=\"searchresults-header\"></div>\n                        <ul id=\"mdbook-searchresults\">\n                        </ul>\n                    </div>\n                </div>\n                {{/if}}\n\n                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->\n                <script>\n                    document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');\n                    document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible');\n                    Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) {\n                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);\n                    });\n                </script>\n\n                <div id=\"mdbook-content\" class=\"content\">\n                    <main>\n                        {{{ content }}}\n                    </main>\n\n                    <nav class=\"nav-wrapper\" aria-label=\"Page navigation\">\n                        <!-- Mobile navigation buttons -->\n                        {{#if previous}}\n                            <a rel=\"prev\" href=\"{{ path_to_root }}{{previous.link}}\" class=\"mobile-nav-chapters previous\" title=\"Previous chapter\" aria-label=\"Previous chapter\" aria-keyshortcuts=\"Left\">\n                                {{#if (eq ../text_direction \"rtl\")}}\n                                {{fa \"solid\" \"angle-right\"}}\n                                {{else}}\n                                {{fa \"solid\" \"angle-left\"}}\n                                {{/if}}\n                            </a>\n                        {{/if}}\n\n                        {{#if next}}\n                            <a rel=\"next prefetch\" href=\"{{ path_to_root }}{{next.link}}\" class=\"mobile-nav-chapters next\" title=\"Next chapter\" aria-label=\"Next chapter\" aria-keyshortcuts=\"Right\">\n                                {{#if (eq ../text_direction \"rtl\")}}\n                                {{fa \"solid\" \"angle-left\"}}\n                                {{else}}\n                                {{fa \"solid\" \"angle-right\"}}\n                                {{/if}}\n                            </a>\n                        {{/if}}\n\n                        <div style=\"clear: both\"></div>\n                    </nav>\n                </div>\n            </div>\n\n            <nav class=\"nav-wide-wrapper\" aria-label=\"Page navigation\">\n                {{#if previous}}\n                    <a rel=\"prev\" href=\"{{ path_to_root }}{{previous.link}}\" class=\"nav-chapters previous\" title=\"Previous chapter\" aria-label=\"Previous chapter\" aria-keyshortcuts=\"Left\">\n                        {{#if (eq ../text_direction \"rtl\")}}\n                        {{fa \"solid\" \"angle-right\"}}\n                        {{else}}\n                        {{fa \"solid\" \"angle-left\"}}\n                        {{/if}}\n                    </a>\n                {{/if}}\n\n                {{#if next}}\n                    <a rel=\"next prefetch\" href=\"{{ path_to_root }}{{next.link}}\" class=\"nav-chapters next\" title=\"Next chapter\" aria-label=\"Next chapter\" aria-keyshortcuts=\"Right\">\n                        {{#if (eq text_direction \"rtl\")}}\n                        {{fa \"solid\" \"angle-left\"}}\n                        {{else}}\n                        {{fa \"solid\" \"angle-right\"}}\n                        {{/if}}\n                    </a>\n                {{/if}}\n            </nav>\n\n        </div>\n\n        <template id=fa-eye>{{fa \"solid\" \"eye\"}}</template>\n        <template id=fa-eye-slash>{{fa \"solid\" \"eye-slash\"}}</template>\n        <template id=fa-copy>{{fa \"regular\" \"copy\"}}</template>\n        <template id=fa-play>{{fa \"solid\" \"play\"}}</template>\n        <template id=fa-clock-rotate-left>{{fa \"solid\" \"clock-rotate-left\"}}</template>\n\n        {{#if live_reload_endpoint}}\n        <!-- Livereload script (if served using the cli tool) -->\n        <script>\n            const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n            const wsAddress = wsProtocol + \"//\" + location.host + \"/\" + \"{{{live_reload_endpoint}}}\";\n            const socket = new WebSocket(wsAddress);\n            socket.onmessage = function (event) {\n                if (event.data === \"reload\") {\n                    socket.close();\n                    location.reload();\n                }\n            };\n\n            window.onbeforeunload = function() {\n                socket.close();\n            }\n        </script>\n        {{/if}}\n\n        {{#if playground_line_numbers}}\n        <script>\n            window.playground_line_numbers = true;\n        </script>\n        {{/if}}\n\n        {{#if playground_copyable}}\n        <script>\n            window.playground_copyable = true;\n        </script>\n        {{/if}}\n\n        {{#if playground_js}}\n        <script src=\"{{ resource \"ace.js\" }}\"></script>\n        <script src=\"{{ resource \"mode-rust.js\" }}\"></script>\n        <script src=\"{{ resource \"editor.js\" }}\"></script>\n        <script src=\"{{ resource \"theme-dawn.js\" }}\"></script>\n        <script src=\"{{ resource \"theme-tomorrow_night.js\" }}\"></script>\n        {{/if}}\n\n        {{#if search_js}}\n        <script src=\"{{ resource \"elasticlunr.min.js\" }}\"></script>\n        <script src=\"{{ resource \"mark.min.js\" }}\"></script>\n        <script src=\"{{ resource \"searcher.js\" }}\"></script>\n        {{/if}}\n\n        <script src=\"{{ resource \"clipboard.min.js\" }}\"></script>\n        <script src=\"{{ resource \"highlight.js\" }}\"></script>\n        <script src=\"{{ resource \"book.js\" }}\"></script>\n\n        <!-- Custom JS scripts -->\n        {{#each additional_js}}\n        <script src=\"{{ resource this}}\"></script>\n        {{/each}}\n\n        {{#if is_print}}\n        {{#if mathjax_support}}\n        <script>\n        window.addEventListener('load', function() {\n            MathJax.Hub.Register.StartupHook('End', function() {\n                window.setTimeout(window.print, 100);\n            });\n        });\n        </script>\n        {{else}}\n        <script>\n        window.addEventListener('load', function() {\n            window.setTimeout(window.print, 100);\n        });\n        </script>\n        {{/if}}\n        {{/if}}\n\n        {{#if fragment_map}}\n        <script>\n            document.addEventListener('DOMContentLoaded', function() {\n                const fragmentMap =\n                    {{{fragment_map}}}\n                ;\n                const target = fragmentMap[window.location.hash];\n                if (target) {\n                    let url = new URL(target, window.location.href);\n                    window.location.replace(url.href);\n                }\n            });\n        </script>\n        {{/if}}\n\n    </div>\n    </body>\n</html>\n"
  },
  {
    "path": "docs/book/utils/go.mod",
    "content": "module sigs.k8s.io/kubebuilder/docs/book/utils\n\ngo 1.24.6\n"
  },
  {
    "path": "docs/book/utils/go.sum",
    "content": ""
  },
  {
    "path": "docs/book/utils/litgo/literate.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"go/scanner\"\n\t\"go/token\"\n\t\"log\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"sigs.k8s.io/kubebuilder/docs/book/utils/plugin\"\n)\n\n// Literate is a plugin that extracts block comments from Go source and\n// interleaves them with the surrounding code as fenced code blocks.\n// It should support all output formats.\n// It's triggered by using the an expression like `{{#literatego ./path/to/source/file.go}}`.\n// The marker `+kubebuilder:docs-gen:collapse=<string>` can be used to collapse a description/code\n// pair into a details block with the given summary.\ntype Literate struct {\n\t// PrettyPathPrunePrefix specifies the prefix, if any to prune off of user-visible paths\n\tPrettyPathPrunePrefix string\n\t// BaseSourcePath specifies the base path to internet-reachable versions of the source code used\n\tBaseSourcePath *url.URL\n}\n\n// SupportsOutput implements plugin.Plugin\nfunc (Literate) SupportsOutput(_ string) bool { return true }\n\n// Process implements plugin.Plugin\nfunc (l Literate) Process(input *plugin.Input) error {\n\tsrcDir := input.Context.Config.Book.Src\n\tif srcDir == \"\" {\n\t\tsrcDir = \"src\" // mdBook 0.5.0 no longer populates this field\n\t}\n\tbookSrcDir := filepath.Join(input.Context.Root, srcDir)\n\n\treturn plugin.EachCommand(&input.Book, \"literatego\", func(chapter *plugin.BookChapter, relPath string) (string, error) {\n\t\tchapterDir := filepath.Dir(chapter.Path)\n\t\tpathInfo := filePathInfo{\n\t\t\tchapterRelativePath: relPath,\n\t\t\tchapterDir:          chapterDir,\n\t\t\tbookSrcDir:          bookSrcDir,\n\t\t}\n\t\tfullPath := pathInfo.FullPath()\n\n\t\t// TODO(directxman12): don't escape root?\n\t\tcontents, err := os.ReadFile(fullPath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unable to import %q: %v\", fullPath, err)\n\t\t}\n\n\t\treturn l.extractContents(contents, pathInfo)\n\t})\n}\n\n// filePathInfo stores different paths to a file, to allow for nicely\n// displaying relative path information.\ntype filePathInfo struct {\n\t// chapterRelativePath is the path relative to the current chapter file\n\tchapterRelativePath string\n\n\t// chapterDir is the directory of the chapter, relative to bookSrcDir\n\tchapterDir string\n\n\t// bookSrcDir is the absolute book source path\n\tbookSrcDir string\n}\n\n// FullPath returns the full, absolute path to the given file on the source filesystem.\nfunc (f filePathInfo) FullPath() string {\n\treturn filepath.Join(f.bookSrcDir, f.chapterDir, f.chapterRelativePath)\n}\n\n// viewablePath returns the internet-viewable path to the given source file\nfunc (f filePathInfo) ViewablePath(baseBookSrcURL url.URL) string {\n\trelPath := filepath.ToSlash(filepath.Join(f.chapterDir, f.chapterRelativePath))\n\toutURL := baseBookSrcURL\n\n\toutURL.Path = path.Join(outURL.Path, relPath)\n\n\treturn outURL.String()\n}\n\n// commentCodePair represents a block of code with some text before it, optionally\n// marked as collapsed with the given \"collapse summary\".\ntype commentCodePair struct {\n\tcomment  string\n\tcode     string\n\tcollapse string\n}\n\n// collapsePrefix is the marker comment that indicates that the previous commentCodePair\n// should be collapsed with the given summary\nvar collapsePrefix = \"+kubebuilder:docs-gen:collapse=\"\n\n// getCollapse checks if the given token is a collapse marker, and\n// extracts the summary if so.\nfunc getCollapse(tok token.Token, lit string) string {\n\tif tok != token.COMMENT {\n\t\treturn \"\"\n\t}\n\n\tif lit[:2] != \"//\" {\n\t\treturn \"\"\n\t}\n\trest := strings.TrimSpace(lit[2:])\n\tif !strings.HasPrefix(rest, collapsePrefix) {\n\t\treturn \"\"\n\t}\n\n\treturn rest[len(collapsePrefix):]\n}\n\n// isBlockComment checks that the given token is a `/* comment */`-style comment,\n// which we consider to be the start of a codeCommentPair\nfunc isBlockComment(tok token.Token, lit string) bool {\n\tif tok != token.COMMENT {\n\t\treturn false\n\t}\n\n\tif len(lit) < 3 || lit[0] != '/' || lit[1] != '*' {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// commentText extracts the text from the given comment, slicing off\n// some common amount of whitespace prefix.\nfunc commentText(raw string, lineOffset int) string {\n\trawBody := raw[2 : len(raw)-2] // chop of the delimiters\n\tlines := strings.Split(rawBody, \"\\n\")\n\tif len(lines) == 0 {\n\t\treturn \"\"\n\t}\n\n\tfor i, line := range lines {\n\t\toffset := lineOffset\n\t\tif len(line) < offset {\n\t\t\toffset = len(line)\n\t\t}\n\t\tlines[i] = strings.TrimLeftFunc(line[:offset], unicode.IsSpace) + line[offset:]\n\t}\n\n\treturn strings.Join(lines, \"\\n\")\n}\n\n// extractPairs extracts all commentCodePairs from the given source code with\n// the given path.  A block starts as soon as the last block ends (or at the\n// beginning of the file), and ends as soon as a block comment is encountered,\n// or if a collapse marker is encountered.\nfunc extractPairs(contents []byte, path string) ([]commentCodePair, error) {\n\tfileSet := token.NewFileSet()\n\tfile := fileSet.AddFile(path, -1, len(contents))\n\tscan := scanner.Scanner{}\n\tvar errs []error\n\tscan.Init(file, contents, func(pos token.Position, msg string) {\n\t\terrs = append(errs, fmt.Errorf(\"error parsing file %s: %s\", pos, msg))\n\t}, scanner.ScanComments)\n\n\t// grab all the different sections\n\tvar pairs []commentCodePair\n\tvar lastPair commentCodePair\n\tlastCodeBlockStart := 0\n\n\tvar tok token.Token\n\tfor tok != token.EOF {\n\t\tvar pos token.Pos\n\t\tvar lit string\n\t\tpos, tok, lit = scan.Scan()\n\t\tcollapse := getCollapse(tok, lit)\n\t\tif collapse != \"\" {\n\t\t\tlastPair.collapse = collapse\n\t\t}\n\t\tif collapse == \"\" && !isBlockComment(tok, lit) {\n\t\t\tcontinue\n\t\t}\n\t\tcodeEnd := file.Offset(pos) - 1\n\t\tif codeEnd-lastCodeBlockStart > 0 {\n\t\t\tlastPair.code = string(contents[lastCodeBlockStart:codeEnd])\n\t\t}\n\t\tpairs = append(pairs, lastPair)\n\t\tif collapse == \"\" {\n\t\t\tline := file.Line(pos)\n\t\t\tlineStart := file.LineStart(line)\n\t\t\tlastPair = commentCodePair{\n\t\t\t\tcomment: commentText(lit, file.Offset(pos)-file.Offset(lineStart)),\n\t\t\t}\n\t\t} else {\n\t\t\tlastPair = commentCodePair{}\n\t\t}\n\t\tlastCodeBlockStart = file.Offset(pos) + len(lit)\n\t}\n\tlastPair.code = string(contents[lastCodeBlockStart:])\n\tpairs = append(pairs, lastPair)\n\n\tif len(errs) > 0 {\n\t\treturn nil, errs[0]\n\t}\n\treturn pairs, nil\n}\n\n// extractContents extracts comment-code pairs from the given named file\n// contents, and then renders the result to markdown.\nfunc (l Literate) extractContents(contents []byte, pathInfo filePathInfo) (string, error) {\n\tpairs, err := extractPairs(contents, pathInfo.FullPath())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tout := new(strings.Builder)\n\n\tout.WriteString(`<div class=\"literate\">`)\n\n\t// write the source so that readers can easily find the code\n\tsourcePath := pathInfo.ViewablePath(*l.BaseSourcePath)\n\tprettyPath := pathInfo.chapterRelativePath\n\tif l.PrettyPathPrunePrefix != \"\" {\n\t\tprunedPath, err := filepath.Rel(l.PrettyPathPrunePrefix, prettyPath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unable to remove path prefix %q from %q: %v\", l.PrettyPathPrunePrefix, prettyPath, err)\n\t\t}\n\t\tprettyPath = prunedPath\n\t}\n\tout.WriteString(fmt.Sprintf(`<cite class=\"literate-source\"><a href=\"%[1]s\">%[2]s</a></cite>`, sourcePath, prettyPath))\n\n\tfor _, pair := range pairs {\n\t\tif pair.collapse != \"\" {\n\t\t\tcollapseClass := \"collapse-code\"\n\t\t\t// Hide low-value sections entirely (licenses, imports, etc)\n\t\t\tif strings.EqualFold(pair.collapse, \"Apache License\") || strings.EqualFold(pair.collapse, \"Imports\") {\n\t\t\t\tcollapseClass = \"collapse-code collapse-hide\"\n\t\t\t}\n\t\t\tout.WriteString(\"<details class=\\\"\" + collapseClass + \"\\\"><summary class=\\\"hljs\\\"><pre class=\\\"hljs\\\"><span class=\\\"collapse-summary\\\">\")\n\t\t\tout.WriteString(pair.collapse)\n\t\t\tout.WriteString(\"</span></pre></summary>\")\n\t\t}\n\t\tif strings.TrimSpace(pair.comment) != \"\" {\n\t\t\tout.WriteString(\"\\n\")\n\t\t\tout.WriteString(removeIndent(pair.comment))\n\t\t}\n\n\t\tif strings.TrimSpace(pair.code) != \"\" {\n\t\t\tout.WriteString(\"\\n\\n```go\")\n\t\t\tout.WriteString(wrapWithNewlines(pair.code))\n\t\t\tout.WriteString(\"```\\n\")\n\t\t}\n\t\tif pair.collapse != \"\" {\n\t\t\tout.WriteString(\"\\n</details>\")\n\t\t}\n\t\t// TODO(directxman12): nice side-by-side sections\n\t}\n\tout.WriteString(`</div>`)\n\n\treturn out.String(), nil\n}\n\n// removeIndent removes any initial indent that gofmt puts in place,\n// because it likes to make our lives harder.\n//\n// If we left them in place, text would turn into legacy markdown codeblocks.\nfunc removeIndent(comment string) string {\n\tlines := strings.Split(comment, \"\\n\")\n\tfor i, line := range lines {\n\t\tif strings.HasPrefix(line, \"\\t\") {\n\t\t\tlines[i] = line[1:]\n\t\t}\n\t}\n\treturn strings.Join(lines, \"\\n\")\n}\n\n// wrapWithNewlines ensures that we begin and end with a newline character.\nfunc wrapWithNewlines(src string) string {\n\tsrc = strings.Trim(src, \"\\n\") // remove newlines first to avoid too many\n\treturn \"\\n\" + src + \"\\n\"\n}\n\nfunc main() {\n\tbaseURL, err := url.Parse(\"https://sigs.k8s.io/kubebuilder/docs/book/src\")\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\tcfg := Literate{\n\t\tPrettyPathPrunePrefix: \"testdata\",\n\t\tBaseSourcePath:        baseURL,\n\t}\n\tif err := plugin.Run(cfg, os.Stdin, os.Stdout, os.Args[1:]...); err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "docs/book/utils/markerdocs/doctypes.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\n// these should be kept in sync with the output from controller-tools\n\ntype DetailedHelp struct {\n\tSummary string `json:\"summary\"`\n\tDetails string `json:\"details\"`\n}\n\ntype Argument struct {\n\tType     string    `json:\"type\"`\n\tOptional bool      `json:\"optional\"`\n\tItemType *Argument `json:\"itemType\"`\n}\n\ntype FieldHelp struct {\n\t// definition\n\tName     string `json:\"name\"`\n\tArgument `json:\",inline\"`\n\n\t// help\n\n\tDetailedHelp `json:\",inline\"`\n}\n\ntype MarkerDoc struct {\n\t// definition\n\n\tName   string `json:\"name\"`\n\tTarget string `json:\"target\"`\n\n\t// help\n\n\tDetailedHelp        `json:\",inline\"`\n\tCategory            string      `json:\"category\"`\n\tDeprecatedInFavorOf *string     `json:\"deprecatedInFavorOf\"`\n\tFields              []FieldHelp `json:\"fields\"`\n}\n\ntype CategoryDoc struct {\n\tCategory string      `json:\"category\"`\n\tMarkers  []MarkerDoc `json:\"markers\"`\n}\n\nfunc (m MarkerDoc) Anonymous() bool {\n\treturn len(m.Fields) == 1 && m.Fields[0].Name == \"\"\n}\n"
  },
  {
    "path": "docs/book/utils/markerdocs/html.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"html\"\n\t\"io\"\n\t\"strings\"\n)\n\n// NB(directxman12): we use this instead of templates to avoid\n// weird issues with whitespace in elements rendered as inline.\n// Writing with templates was getting tricky to do without\n// compromising readability.\n//\n// This isn't an amazing solution, but it's good enough™\n\n// toHTML knows how to write itself as HTML to an output.\ntype toHTML interface {\n\t// WriteHTML writes this as HTML to the given Writer.\n\tWriteHTML(io.Writer) error\n}\n\n// Text is a chunk of text in an HTML doc.\ntype Text string\n\n// WriteHTML writes the string as HTML to the given Writer while accounting for mdBook's handling\n// of backticks. Given mdBook's behavior of treating backticked content as raw text, this function\n// ensures proper rendering by preventing unnecessary HTML escaping within code snippets. Chunks\n// outside of backticks are HTML-escaped for security, while chunks inside backticks remain as raw\n// text, preserving mdBook's intended rendering of code content.\nfunc (t Text) WriteHTML(w io.Writer) error {\n\ttextChunks := strings.Split(string(t), \"`\")\n\n\tfor i, chunk := range textChunks {\n\t\tif i%2 == 0 { // Outside backticks, escape and write HTML\n\t\t\t_, err := io.WriteString(w, html.EscapeString(chunk))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else { // Inside backticks, write raw HTML\n\t\t\t_, err := io.WriteString(w, \"`\"+chunk+\"`\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Tag is some tag with contents and attributes in an HTML doc.\ntype Tag struct {\n\tName     string\n\tAttrs    Attrs\n\tChildren []toHTML\n}\n\n// WriteHTML writes the tag as HTML to the given Writer\nfunc (t Tag) WriteHTML(w io.Writer) error {\n\tattrsOut := \"\"\n\tif t.Attrs != nil {\n\t\tattrsOut = t.Attrs.ToAttrs()\n\t}\n\tif _, err := fmt.Fprintf(w, \"<%s %s>\", t.Name, attrsOut); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, child := range t.Children {\n\t\tif err := child.WriteHTML(w); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif _, err := fmt.Fprintf(w, \"</%s>\", t.Name); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Fragment is some series of tags, text, etc in an HTML doc.\ntype Fragment []toHTML\n\n// WriteHTML writes the fragment as HTML to the given Writer\nfunc (f Fragment) WriteHTML(w io.Writer) error {\n\tfor _, item := range f {\n\t\tif err := item.WriteHTML(w); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Attrs knows how to convert itself to HTML attributes.\ntype Attrs interface {\n\t// ToAttrs returns `key1=\"value1\" key2=\"value2\"` etc to be placed into an HTML tag.\n\tToAttrs() string\n}\n\n// classes sets the class attribute to these class names.\ntype classes []string\n\n// ToAttrs implements Attrs\nfunc (c classes) ToAttrs() string { return fmt.Sprintf(\"class=%q\", strings.Join(c, \" \")) }\n\n// optionalClasses sets the class attribute to these class names, if their values are true.\ntype optionalClasses map[string]bool\n\n// ToAttrs implements Attrs\nfunc (c optionalClasses) ToAttrs() string {\n\tactualClasses := make([]string, 0, len(c))\n\tfor class, active := range c {\n\t\tif active {\n\t\t\tactualClasses = append(actualClasses, class)\n\t\t}\n\t}\n\treturn classes(actualClasses).ToAttrs()\n}\n\n// attrs joins together one or more Attrs.\ntype attrs []Attrs\n\n// ToAttrs implements Attrs\nfunc (a attrs) ToAttrs() string {\n\tparts := make([]string, len(a))\n\tfor i, attr := range a {\n\t\tparts[i] = attr.ToAttrs()\n\t}\n\treturn strings.Join(parts, \" \")\n}\n\n// dataAttr represents some `data-*` attribute.\ntype dataAttr struct {\n\tName  string\n\tValue string\n}\n\n// ToAttrs implements Attrs\nfunc (d dataAttr) ToAttrs() string {\n\treturn fmt.Sprintf(\"data-%s=%q\", d.Name, d.Value)\n}\n\n// makeTag produces a function that makes tags of the given\n// type.\nfunc makeTag(name string) func(Attrs, ...toHTML) Tag {\n\treturn func(attrs Attrs, children ...toHTML) Tag {\n\t\treturn Tag{\n\t\t\tName:     name,\n\t\t\tAttrs:    attrs,\n\t\t\tChildren: children,\n\t\t}\n\t}\n}\n\nvar (\n\tdd      = makeTag(\"dd\")\n\tdt      = makeTag(\"dt\")\n\tdl      = makeTag(\"dl\")\n\tdetails = makeTag(\"details\")\n\tsummary = makeTag(\"summary\")\n\tspan    = makeTag(\"span\")\n\tdiv     = makeTag(\"div\")\n)\n"
  },
  {
    "path": "docs/book/utils/markerdocs/main.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/docs/book/utils/plugin\"\n)\n\n// argType produces HTML for describing an Argument's type.\nfunc argType(arg *Argument) toHTML {\n\tif arg.Type == \"slice\" {\n\t\treturn span(optionalClasses{\"optional\": arg.Optional, \"slice\": true},\n\t\t\targType(arg.ItemType))\n\t}\n\n\treturn span(classes{\"optional\"},\n\t\tText(arg.Type))\n}\n\n// maybeDetails returns HTML describing summary and\n// details if present, otherwise returning an empty fragment.\nfunc maybeDetails(help *DetailedHelp) toHTML {\n\tif help.Summary == \"\" && help.Details == \"\" {\n\t\treturn Fragment{}\n\t}\n\n\treturn Fragment{\n\t\tdetails(nil,\n\t\t\tsummary(optionalClasses{\"no-details\": help.Details == \"\"},\n\t\t\t\tText(help.Summary)),\n\t\t\t// NB(directxman12): if we don't wrap with newlines, markdown won't be parsed\n\t\t\tText(wrapWithNewlines(help.Details)))}\n}\n\n// markerTemplate returns HTML describing the documentation for a given marker.\nfunc markerTemplate(marker *MarkerDoc) toHTML {\n\n\t// the marker name\n\tterm := dt(classes{\"literal\", \"name\"},\n\t\tText(\"// +\"+marker.Name))\n\n\t// the args summary (displayed in summary mode)\n\tvar fields []toHTML\n\tfor _, field := range marker.Fields {\n\t\tfields = append(fields, Fragment{\n\t\t\tdt(optionalClasses{\"argument\": true, \"optional\": field.Optional, \"literal\": true},\n\t\t\t\tText(field.Name)),\n\t\t\tdd(optionalClasses{\"argument\": true, \"type\": true, \"optional\": field.Optional},\n\t\t\t\targType(&field.Argument)),\n\t\t})\n\t}\n\targsDef := dd(classes{\"args\"},\n\t\tdl(classes{\"args\", \"summary\"},\n\t\t\tfields...))\n\n\t// the argument details (displayed in details mode)\n\tvar args Fragment\n\tfor _, field := range marker.Fields {\n\t\targs = append(args, Fragment{\n\t\t\tdt(optionalClasses{\"argument\": true, \"optional\": field.Optional, \"literal\": true},\n\t\t\t\tText(field.Name)),\n\t\t\tdd(optionalClasses{\"argument\": true, \"type\": true, \"optional\": field.Optional},\n\t\t\t\targType(&field.Argument)),\n\t\t\tdd(classes{\"description\"},\n\t\t\t\tmaybeDetails(&field.DetailedHelp))})\n\t}\n\n\t// the help (displayed in both modes)\n\thelpDef := dd(classes{\"description\"},\n\t\tmaybeDetails(&marker.DetailedHelp),\n\t\tdl(classes{\"args\"},\n\t\t\targs))\n\n\t// the overall wrapping marker (common classes go here to make it easier to select\n\t// on certain things w/o duplication)\n\tmarkerAttrs := attrs{\n\t\toptionalClasses{\n\t\t\t\"marker\":     true,\n\t\t\t\"deprecated\": marker.DeprecatedInFavorOf != nil,\n\t\t\t\"anonymous\":  marker.Anonymous(),\n\t\t},\n\t\tdataAttr{Name: \"target\", Value: marker.Target},\n\t}\n\tif marker.DeprecatedInFavorOf != nil {\n\t\tmarkerAttrs = append(markerAttrs, dataAttr{Name: \"deprecated\", Value: *marker.DeprecatedInFavorOf})\n\t}\n\treturn div(markerAttrs,\n\t\tterm, argsDef, helpDef)\n}\n\n// MarkerDocs is a plugin that autogenerates documentation\n// for markers known to controller-gen.  Generated pages\n// will be added to locations marked `{{#markerdocs category name}}`.\n// This allows us to put additional documentation in each category.\ntype MarkerDocs struct {\n\t// Args contains the arguments to pass to controller-gen to get\n\t// marker help JSON output.  Each key is a prefix to apply to\n\t// category names (for disambiguation), and each value is an invocation\n\t// of controller-gen.\n\tArgs map[string][]string\n}\n\n// SupportsOutput implements plugin.Plugin\nfunc (MarkerDocs) SupportsOutput(_ string) bool { return true }\n\n// Process implements plugin.Plugin\nfunc (p MarkerDocs) Process(input *plugin.Input) error {\n\tmarkerDocs, err := p.getMarkerDocs()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to fetch marker docs: %v\", err)\n\t}\n\n\t// first, find all categories...\n\tmarkersByCategory := make(map[string][]MarkerDoc)\n\tfor _, cat := range markerDocs {\n\t\tif cat.Category == \"\" {\n\t\t\t// skip un-named categories, which are intended to be hidden\n\t\t\t// (e.g. all the per-generate idendical output rules)\n\t\t\tcontinue\n\t\t}\n\t\tmarkersByCategory[cat.Category] = cat.Markers\n\t}\n\n\tusedCategories := make(map[string]struct{}, len(markersByCategory))\n\n\t// NB(directxman12): we use existing pages instead of generating new ones so that we can add additional\n\t// content to the pages (for instance, additional description of the category).\n\n\t// ...then, go through the book, finding all instances of `{{#markerdocs <category>}}` and replacing them\n\t// with the appropriate docs ...\n\terr = plugin.EachCommand(&input.Book, \"markerdocs\", func(chapter *plugin.BookChapter, category string) (string, error) {\n\t\tcategory = strings.TrimSpace(category)\n\t\tmarkers, knownCategory := markersByCategory[category]\n\t\tif !knownCategory {\n\t\t\treturn \"\", fmt.Errorf(\"unknown category %q\", category)\n\t\t}\n\n\t\t// HTML5 says that any characters are valid in ID except for space,\n\t\t// but may not be empty (which we prevent by skipping un-named categories):\n\t\t// https://www.w3.org/TR/html52/dom.html#element-attrdef-global-id\n\t\tcategoryAlias := strings.ReplaceAll(category, \" \", \"-\")\n\n\t\tcontent := new(strings.Builder)\n\n\t\t// NB(directxman12): wrap this in a div to prevent the markdown processor from inserting extra paragraphs\n\t\t_, err := fmt.Fprintf(content, \"<div><input checked type=\\\"checkbox\\\" class=\\\"markers-summarize\\\" id=\\\"markers-summarize-%[1]s\\\"><label class=\\\"markers-summarize\\\" for=\\\"markers-summarize-%[1]s\\\">Show Detailed Argument Help</label><dl class=\\\"markers\\\">\", categoryAlias)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unable to render marker documentation summary: %v\", err)\n\t\t}\n\n\t\t// write the markers\n\t\tfor _, marker := range markers {\n\t\t\tif err := markerTemplate(&marker).WriteHTML(content); err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"unable to render documentation for marker %q: %v\", marker.Name, err)\n\t\t\t}\n\t\t}\n\n\t\tif _, err = fmt.Fprintf(content, \"</dl></div>\"); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unable to render marker documentation: %v\", err)\n\t\t}\n\n\t\tusedCategories[category] = struct{}{}\n\n\t\treturn content.String(), nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// ... and finally make sure we didn't miss any\n\tif len(usedCategories) != len(markersByCategory) {\n\t\tunusedCategories := make([]string, 0, len(markersByCategory)-len(usedCategories))\n\t\tfor cat := range markersByCategory {\n\t\t\tif _, ok := usedCategories[cat]; !ok {\n\t\t\t\tunusedCategories = append(unusedCategories, cat)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"unused categories %v\", unusedCategories)\n\t}\n\n\treturn nil\n}\n\n// wrapWithNewlines ensures that we begin and end with a newline character.\n// this is important to ensure that markdown is parsed inside of details elements.\nfunc wrapWithNewlines(src string) string {\n\tif len(src) < 4 {\n\t\treturn src\n\t}\n\tif src[0] != '\\n' {\n\t\tsrc = \"\\n\" + src\n\t}\n\tif src[1] != '\\n' {\n\t\tsrc = \"\\n\" + src\n\t}\n\tif src[len(src)-1] != '\\n' {\n\t\tsrc = src + \"\\n\"\n\t}\n\tif src[len(src)-2] != '\\n' {\n\t\tsrc = src + \"\\n\"\n\t}\n\treturn src\n}\n\n// getMarkerDocs fetches marker documentation from controller-gen\nfunc (p MarkerDocs) getMarkerDocs() ([]CategoryDoc, error) {\n\tvar res []CategoryDoc\n\tfor categoryPrefix, args := range p.Args {\n\t\tcmd := exec.Command(\"controller-gen\", args...)\n\t\toutRaw, err := cmd.Output()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar invocationRes []CategoryDoc\n\t\tif err := json.Unmarshal(outRaw, &invocationRes); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor i, category := range invocationRes {\n\t\t\t// leave empty categories as-is, so that they're skipped\n\t\t\tif category.Category == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tinvocationRes[i].Category = categoryPrefix + category.Category\n\t\t}\n\n\t\tres = append(res, invocationRes...)\n\t}\n\n\treturn res, nil\n}\n\nfunc main() {\n\tif err := plugin.Run(MarkerDocs{\n\t\tArgs: map[string][]string{\n\t\t\t// marker args\n\t\t\t\"\": {\"-wwww\", \"crd\", \"webhook\", \"rbac:roleName=cheddar\" /* role name doesn't mean anything here */, \"object\", \"schemapatch:manifests=.\"},\n\t\t\t// cli options args\n\t\t\t\"CLI: \": {\"-hhhh\"},\n\t\t},\n\t}, os.Stdin, os.Stdout, os.Args[1:]...); err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n}\n"
  },
  {
    "path": "docs/book/utils/plugin/input.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\n// Context is a (partial) mdBook execution context.\ntype Context struct {\n\tRoot   string `json:\"root\"`\n\tConfig Config `json:\"config\"`\n}\n\n// Config is a (partial) mdBook config\ntype Config struct {\n\tBook BookConfig `json:\"book\"`\n}\n\n// BookConfig is a (partial) mdBook [book] stanza\ntype BookConfig struct {\n\tSrc string `json:\"src\"`\n}\n\n// Book is an mdBook book.\ntype Book struct {\n\tItems         []BookItem `json:\"items\"`\n\tNonExhaustive *struct{}  `json:\"__non_exhaustive\"`\n}\n\n// BookSection is an mdBook section.\ntype BookSection struct {\n\tItems []BookItem `json:\"items\"`\n}\n\n// BookItem is an mdBook item.\n// It wraps an underlying struct to provide proper marshalling and unmarshalling\n// according to what serde produces/expects.\ntype BookItem bookItem\n\n// UnmarshalJSON implements encoding/json.Unmarshaler\nfunc (b *BookItem) UnmarshalJSON(input []byte) error {\n\t// match how serde serializes rust enums.\n\tif input[0] == '\"' {\n\t\t// actually a an empty variant\n\t\tvar variant string\n\t\tif err := json.Unmarshal(input, &variant); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tswitch variant {\n\t\tcase \"Separator\":\n\t\t\tb.Separator = true\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown book item variant %s\", variant)\n\t\t}\n\t\treturn nil\n\t}\n\n\titem := bookItem(*b)\n\tif err := json.Unmarshal(input, &item); err != nil {\n\t\treturn err\n\t}\n\t*b = BookItem(item)\n\treturn nil\n}\n\n// MarshalJSON implements encoding/json.Marshaler\nfunc (b BookItem) MarshalJSON() ([]byte, error) {\n\tif b.Separator {\n\t\treturn json.Marshal(\"Separator\")\n\t}\n\n\treturn json.Marshal(bookItem(b))\n}\n\n// bookItem is the underlying mdBook item without custom serialization.\ntype bookItem struct {\n\tChapter   *BookChapter `json:\"Chapter\"`\n\tSeparator bool         `json:\"-\"`\n}\n\n// BookChapter is an mdBook chapter.\ntype BookChapter struct {\n\tName        string        `json:\"name\"`\n\tContent     string        `json:\"content\"`\n\tNumber      SectionNumber `json:\"number\"`\n\tSubItems    []BookItem    `json:\"sub_items\"`\n\tPath        string        `json:\"path\"`\n\tParentNames []string      `json:\"parent_names\"`\n}\n\n// SectionNumber is an mdBook section number (e.g. `1.2` is `{1,2}`).\ntype SectionNumber []uint32\n\n// Input is the tuple that's presented to mdBook plugins.\n// It's deserialized from a slice `[context, book]`, matching\n// a Rust tuple.\ntype Input struct {\n\tContext Context\n\tBook    Book\n}\n\n// UnmarshalJSON implements encoding/json.Unmarshaler\nfunc (p *Input) UnmarshalJSON(input []byte) error {\n\t// deserialize from the JSON equivalent to the Rust tuple\n\t// `(context, book)`\n\tinputBuffer := bytes.NewBuffer(input)\n\tdec := json.NewDecoder(inputBuffer)\n\n\ttok, err := dec.Token()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif delim, isDelim := tok.(json.Delim); !isDelim || delim != '[' {\n\t\treturn fmt.Errorf(\"expected [, got %s\", tok)\n\t}\n\tif err := dec.Decode(&p.Context); err != nil {\n\t\treturn err\n\t}\n\tif err := dec.Decode(&p.Book); err != nil {\n\t\treturn err\n\t}\n\ttok, err = dec.Token()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif delim, isDelim := tok.(json.Delim); !isDelim || delim != ']' {\n\t\treturn fmt.Errorf(\"expected ], got %s\", tok)\n\t}\n\n\treturn nil\n}\n\n// ChapterVisitor visits each BookChapter in a book, getting an actual\n// pointer to the chapter that it can modify.\ntype ChapterVisitor func(*BookChapter) error\n\n// EachItem calls the given visitor for each chapter in the given item,\n// passing a pointer to the actual chapter that the visitor can modify.\nfunc EachItem(parentItem *BookItem, visitor ChapterVisitor) error {\n\tif parentItem.Chapter == nil {\n\t\treturn nil\n\t}\n\n\tif err := visitor(parentItem.Chapter); err != nil {\n\t\treturn err\n\t}\n\n\t// pass a pointer to the structure, not the iteration variable\n\tfor i := range parentItem.Chapter.SubItems {\n\t\tif err := EachItem(&parentItem.Chapter.SubItems[i], visitor); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// EachItemInBook functions identically to EachItem, except that it visits\n// all chapters in the book.\nfunc EachItemInBook(book *Book, visitor ChapterVisitor) error {\n\t// pass a pointer to the structure, not the iteration variable\n\tfor i := range book.Items {\n\t\tif err := EachItem(&book.Items[i], visitor); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/utils/plugin/plugin.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n)\n\n// Plugin represents a mdBook plugin.\ntype Plugin interface {\n\t// SupportsOutput checks if the given plugin supports the given output format.\n\tSupportsOutput(string) bool\n\t// Process modifies the book in the input, which gets returned as the result of the plugin.\n\tProcess(*Input) error\n}\n\n// Run runs the given plugin on the given input stream, outputting its result to the given\n// result, assuming the given command-line args (without program name).\nfunc Run(plug Plugin, inputRaw io.Reader, outputRaw io.Writer, args ...string) error {\n\tif len(args) > 1 && args[0] == \"supports\" {\n\t\t// we support any renderer, no need to check (name is in Args[1])\n\t\tif plug.SupportsOutput(args[1]) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"output format %q not supported\", args[1])\n\t}\n\n\tvar input Input\n\tdec := json.NewDecoder(inputRaw)\n\tif err := dec.Decode(&input); err != nil {\n\t\treturn fmt.Errorf(\"unable to decode preprocessor input: %v\", err)\n\t}\n\n\tif err := plug.Process(&input); err != nil {\n\t\treturn err\n\t}\n\n\tout, err := json.Marshal(&input.Book)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to encode output book object: %v\", err)\n\t}\n\n\tif n, err := outputRaw.Write(out); err != nil || n < len(out) {\n\t\tif err == nil && n < len(out) {\n\t\t\terr = io.ErrShortWrite\n\t\t}\n\t\treturn fmt.Errorf(\"unable to write output book object: %v\", err)\n\t}\n\n\tif err = os.WriteFile(\"/tmp/litout.json\", out, os.ModePerm); err != nil {\n\t\treturn fmt.Errorf(\"unable to write output book object: %v\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "docs/book/utils/plugin/utils.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// EachCommand looks for mustache-like declarations of the form `{{#cmd args}}`\n// in each book chapter, and calls the callback for each one, substituting it\n// for the result.\nfunc EachCommand(book *Book, cmd string, callback func(chapter *BookChapter, args string) (string, error)) error {\n\tcmdStart := fmt.Sprintf(\"{{#%s \", cmd)\n\treturn EachItemInBook(book, func(chapter *BookChapter) error {\n\t\tif chapter.Content == \"\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// figure out all the trigger expressions\n\t\tpartsRaw := strings.Split(chapter.Content, cmdStart)\n\t\t// the first section won't start with `{{#<cmd> ` as per how split works\n\t\tif len(partsRaw) < 2 {\n\t\t\treturn nil\n\t\t}\n\n\t\tvar res []string\n\t\tres = append(res, partsRaw[0])\n\t\tfor _, part := range partsRaw[1:] {\n\t\t\tendDelim := strings.Index(part, \"}}\")\n\t\t\tif endDelim < 0 {\n\t\t\t\treturn fmt.Errorf(\"missing end delimiter in chapter %q\", chapter.Name)\n\t\t\t}\n\t\t\tnewContents, err := callback(chapter, part[:endDelim])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tres = append(res, newContents)\n\t\t\tres = append(res, part[endDelim+2:])\n\t\t}\n\n\t\tchapter.Content = strings.Join(res, \"\")\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "docs/kubebuilder_annotation.md",
    "content": "# Kubebuilder Annotation\n\nIf you have been using Kubebuilder, you must have seen comments such as `// +kubebuilder:rbac: ....` , `// +kubebuilder:resource:...` in the scaffolder Go files. These special comments are used by kubebuilder tools (controller tools) to generate CRD, RBAC, and webhook manifests. In kubebuilder, these special comments are `Kubebuilder Annotation`, a.k.a `annotation`. It is designed for this kind of use case: To use kubebuilder tools, all you have to do is focus on writing your code, and put instructions with parameters as annotations along with your code, so that everything will be handled based on these annotations instructions by kubebuilder. This document illustrates the syntax of these annotations.\n\n## Kubebuilder Annotation Syntax\n\nKubebuilder Annotation has a series of tokens separated by colons into groups from left to right. Each **Token** is a string identifier in an annotation instance. It has meaning by its position in a token slice, in the form of\n**+[header]:[module]:[submodule]:[key-value elements]**\nGo Annotation starts with `+` (e.g. `// +kubebuilder`) to differentiate from regular go comments.\n\n## Token types\n\n- **header** is the identifier of a group of annotations. It helps the user know which project provides this annotation. For example, in the Kubernetes project, headers like `kubebuilder`, `k8s`, `genclient`, etc. are all project identifiers. A header is required for all annotations, since you may use multiple annotations from different projects in the same codebase.\n\n- **module** is the identifier of a functional module in an annotation. An annotation may have a group of modules, each of which performs a particular function.\n\n- **submodule** (optional) In some cases, the module has a big functional scope, split into fine-grained sub-modules, which provide the flexibility of extending module functionality. For example: **module:submodule1:submodule2:submodule3** submodule can be multiple following one by one.\n\n## Levels of symbols\n\nDelimiter symbols are distinguished to work in different levels from top-down for splitting values string in tokens, which provides readability and efficiency.\n\n- **Colon**\n\n  Colon `:` is the 1st level delimiter (to annotation) only for separate tokens. Tokens on different sides of the colon should refer to different token types.\n\n- **Comma**\n\n  Comma `,` is the 2nd level delimiter (to annotation) for splitting key-value pairs in **key-value elements** which is normally the last token in the annotation. e.g. `+kubebuilder:printcolumn:name=<name>,type=<type>,description=<desc>,JSONPath:<.spec.Name>,priority=<int32>,format=<format>` It works within token which is the 2nd level of annotation, so it is called \"2nd level delimiter\"\n\n- **Equal sign**\n\n  Equal sign `=` is the 3rd level delimiter (to annotation) for identifying key and value. Since the `key=value` parts are split from single token (2nd level), its inner delimiter `=` works for next level (3rd level)\n\n- **Semicolon sign**\n\n  Semicolon sign `;` is the 4th level delimiter, which works on the `value` part (4th level) of `key=value`(3rd level) for splitting individual values. e.g. `key1=value1;value2;value3`\n\n- **Pipe sign or Vertical bar**\n\n  Pipe sign `|` is the 5th level delimiter, which works inside the single `value` part (4th level) indicating key and value in case of the single value has nested key-value structure. e.g. `outerkey=innerkey1|innervalue1`\n\n### Examples\n\n#### Webhook annotation examples\n\n**[header]** is `kubebuilder`,\n**[module]** is `webhook`,\n**[submodule]** is `admission` or `serveroption`\n\n```golang\n// +kubebuilder:webhook:admission:groups=apps,resources=deployments,verbs=CREATE;UPDATE,name=bar-webhook,path=/bar,type=mutating,failure-policy=Fail\n\n// +kubebuilder:webhook:serveroption:port=7890,cert-dir=/tmp/test-cert,service=test-system|webhook-service,selector=app|webhook-server,secret=test-system|webhook-secret,mutating-webhook-config-name=test-mutating-webhook-cfg,validating-webhook-config-name=test-validating-webhook-cfg\n```\n\n**Notes:**\n\n1. Separate two `submodule` (categories) under `webhook`: 1) `admission`and 2) `serveroption`, handling webhookTags and serverTags separately.\n2. For each submodule, all key values should put in the same comment line.\n3. using `|` for splitting key value of `lables`\n\n#### RBAC Annotation examples\n\n**[header]** is `kubebuilder`\n**[module]** is `rbac`\nNo submodule at this moment, support annotations like: `// +rbac`, `// +kubebuilder:rbac`\n\n```golang\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;delete\n\n// +rbac:groups=apps,resources=deployments,verbs=get;list;watch;delete\n```\n"
  },
  {
    "path": "docs/kubebuilder_v0_v1_difference.md",
    "content": "# Kubebuilder v0 vs. v1\n\nKubebuilder 1.0 adds a new flag `--project-version`, it accepts two different values, `v0` and `v1`. When `v0` is used, the kubebuilder behavior and workflow are the same as kubebuilder 0.*. When `v1` is specified, the generated v1 project layout is architecturally different from v0 project. v1 project use [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) set of libraries for controller implementation and used tools under [controller-tools](https://github.com/kubernetes-sigs/controller-tools) for scaffolding and generation.\n\n\n## Command difference\n  - kubebuilder v0 has `init`, `create controller`, `create resource`, `create config`, `generate` commands and the workflow is:\n\n```\n  kubebuilder init --domain example.com\n  kubebuilder create resource --group <group> --version <version> --kind <Kind>\n  GOBIN=${PWD}/bin go install ${PWD#$GOPATH/src/}/cmd/controller-manager\n  bin/controller-manager --kubeconfig ~/.kube/config\n\n  kubectl apply -f hack/sample/<resource>.yaml\n  docker build -f Dockerfile.controller . -t <image:tag>\n  docker push <image:tag>\n  kubebuilder create config --controller-image <image:tag> --name <project-name>\n  kubectl apply -f hack/install.yaml\n```\n\n  Every time the resource or controller is updated, users need to run `kubebuilder generate` to regenerate the project.\n  - kubebuilder v1 has `init`, `create api` commands and the workflow is\n\n```\n  kubebuilder init --domain example.com --license apache2 --owner \"The Kubernetes authors\"\n  kubebuilder create api --group ship --version v1beta1 --kind Frigate\n  make install\n  make run\n```\n\n  In a v1 project, there is no generate command. When the resource or controller is updated, users don't need to regenerate the project.\n\n## Scaffolding difference\n\n- v0 project contains a directory `pkg/client` while v1 project doesn't\n- v0 project contains a directory `inject` while v1 project doesn't\n- v0 project layout follows predefined directory layout `pkg/apis` and `pkg/controller` while v1 project accepts user-specified path\n- In v1 project, there is a `init()` function for every api and controller.\n\n## Library difference\n### Controller libraries\n  - v0 projects import the controller library from kubebuilder `kubebuilder/pkg/controller`. It provides a `GenericController` type with a list of functions.\n\n  - v1 projects import the controller libraries from controller-runtime, such as `controller-runtime/pkg/controller`, `controller-runtime/pkg/reconcile`.\n\n### Client libraries\n\n  - In v0 projects, the client libraries is generated by `kubebuilder generate` under directory `pkg/client` and imported wherever they are used in the project.\n\n  - v1 projects import the dynamic client library from controller-runtime `controller-runtime/pkg/client`.\n\n## Wiring difference\nWiring refers to the mechanics of integrating controllers into controller-managers and injecting the dependencies in them.\n  - v0 projects have an `inject` package and it provides functions for adding the controller to controller-manager as well as registering CRDs.\n  - v1 projects don't have a `inject` package, the controller is added to controller-manager by a `init` function inside add_<type>.go file inside the controller directory. The types are registered by an `init` function inside <type>_types.go file inside the apis directory.\n"
  },
  {
    "path": "docs/migration_guide.md",
    "content": "# Migration guide from v0 project to v1 project\n\nThis document describes how to migrate a project created by kubebuilder v0 to a project created by kubebuilder v1. Before jumping into the detailed instructions, please take a look at the list of [major differences between kubebuilder v0 and kubebuilder v1](kubebuilder_v0_v1_difference.md).\n\nThe recommended way of migrating a v0 project to a v1 project is to create a new v1 project and copy/modify the code from v0 project to it.\n\n## Init a v1 project\nFind the project's domain name from the old project's pkg/apis/doc.go and use it to initiate a new project with\n`kubebuilder init --project-version v1 --domain <domain>`\n\n## Create api\nFind the group/version/kind names from the project's pkg/apis. The group and version names are directory names while the kind name can be found from *_types.go. Note that the kind name should be capitalized.\n\nCreate api in the new project with\n`kubebuilder create api --group <group> --version <version> --kind <kind>`\n\nIf there are several resources in the old project, repeat the `kubebuilder create api` command to create all of them.\n\n## Copy types.go\nCopy the content of `<type>_types.go` from the old project into the file `<type>_types.go` in the new project.\nNote that in the v1 project, there is a section containing `<type>List` and `init` functions. Please keep this section.\n```\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +genclient:nonNamespaced\n\n// HelloList contains a list of Hello\ntype HelloList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []Hello `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Hello{}, &HelloList{})\n}\n```\n\n## Copy and modify controller code\n\n### copy and update reconcile function\nNote that in v0 and v1 projects, the `Reconcile`\nfunctions have different arguments and return types.\n\n- `Reconcile` function in v0 project: `func (bc *<kind>Controller) Reconcile(k types.ReconcileKey) error`\n\n- `Reconcile` function in v1 project: `func (r *Reconcile<kind>) Reconcile(request reconcile.Request) (reconcile.Result, error)`\n\nRemove the original body of `Reconcile` function inside the v1 project and copy the body of the `Reconcile` function from the v0 project to the v1 project. Then apply following changes:\n- add `reconcile.Result{}` as the first value in every `return` statement\n- change the call of client functions such as `Get`, `Create`, `Update`. In v0 projects, the call of client functions has the format like `bc.<kind>Lister.<kind>().Get()` or `bc.KubernetesClientSet.<group>.<version>.<Kind>.Get()`. They can be replaced by `r.Client` functions. Here are several examples of updating the client function from v0 project to v1 project:\n\n```\n# in v0 project\nmc, err := bc.memcachedLister.Memcacheds(k.Namespace).Get(k.Name)\n# in v1 project, change to\nmc := &myappsv1alpha1.Memcached{}\nerr := r.Client.Get(context.TODO(), request.NamespacedName, mc)\n\n\n# in v0 project\ndp, err := bc.KubernetesInformers.Apps().V1().Deployments().Lister().Deployments(mc.Namespace).Get(mc.Name)\n# in v1 project, change to\ndp := &appsv1.Deployment{}\nerr := r.Client.Get(context.TODO(), request.NamespacedName, dp)\n\n\ndep := &appsv1.Deployment{...}\n# in v0 project\ndp, err := bc.KubernetesClientSet.AppsV1().Deployments(mc.Namespace).Create(dep)\n# in v1 project, change to\nerr := r.Client.Create(context.TODO(), dep)\n\n\ndep := &appsv1.Deployment{...}\n# in v0 project\ndp, err = bc.KubernetesClientSet.AppsV1().Deployments(mc.Namespace).Update(deploymentForMemcached(mc))\n# in v1 project, change to\nerr := r.Client.Update(context.TODO(), dep)\n\n\nlabelSelector := labels.SelectorFrom{...}\n# in v0 project\npods, err := bc.KubernetesInformers.Core().V1().Pods().Lister().Pods(mc.Namespace).List(labelSelector)\n# in v1 project, change to\npods := &v1.PodList{}\nerr = r.Client.List(context.TODO(), &client.ListOptions{LabelSelector: labelSelector}, pods)\n```\n- add library imports used in the v0 project to v1 project such as log, fmt or k8s libraries. Note that libraries from kubebuilder or from the old project's client package shouldn't be added.\n\n\n### update add function\n\nIn a v0 project controller file, there is a `ProvideController` function creating a controller and adding some watches. In v1 projects, the corresponding function is `add`. For this part, you don't need to copy any code from the v0 project to v1 project. You need to add some watchers in the v1 project's `add` function based on what `watch` functions are called in the v0 project's `ProvideController` function.\n\nHere are several examples:\n\n```\ngc := &controller.GenericController{...}\ngc.Watch(&myappsv1alpha1.Memcached{})\ngc.WatchControllerOf(&v1.Pod{}, eventhandlers.Path{bc.LookupRS, bc.LookupDeployment, bc.LookupMemcached})\n```\n\nneed to be changed to:\n\n```\nc, err := controller.New{...}\nc.Watch(&source.Kind{Type: &myappsv1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{})\nc.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{\n\t\tIsController: true,\n\t\tOwnerType:    &myappsv1alpha1.Memcached{},\n\t})\n```\n\n### copy other functions\nIf the `reconcile` function depends on some other user-defined functions, copy those functions as well into the v1 project.\n\n## Copy user libraries\nIf there are some user-defined libraries in the old project, make sure to copy them as well into the new project.\n\n## Update dependency\n\nOpen the Gopkg.toml file in the old project and find if there is user-defined dependency in this block:\n\n```\n# Users add deps lines here\n\n[prune]\n  go-tests = true\n  #unused-packages = true\n\n# Note: Stanzas below are generated by Kubebuilder and may be rewritten when\n# upgrading kubebuilder versions.\n\n# DO NOT MODIFY BELOW THIS LINE.\n```\nCopy those dependencies into the new project's Gopkg.toml file **before** the line\n```\n# STANZAS BELOW ARE GENERATED AND MAY BE WRITTEN - DO NOT MODIFY BELOW THIS LINE.\n```\n\n## Copy other user files\nIf there are other user-created files in the old project, such as any build scripts, or README.md files. Copy those files into the new project.\n\n## Confirmation\nRun `make` to make sure the new project can be built and pass all the tests.\nRun `make install` and `make run` to make sure the api and controller work well on cluster.\n"
  },
  {
    "path": "docs/testing/e2e.md",
    "content": "**Running End-to-end Tests on Remote Clusters**\n\n**This document is for kubebuilder v1 only**\n\nThis article outlines steps to run e2e tests on remote clusters for controllers created using `kubebuilder`. For example, after developing a database controller, the developer may want to run some e2e tests on a GKE cluster to verify the controller is working as expected. Currently, `kubebuilder` does not provide a template for running the e2e tests. This article serves to address this deficit.\n\nThe steps are as follows:\n1.  Create a test file named `<some-file-name>_test.go` populated with template below (referring [this](https://github.com/foxish/application/blob/master/e2e/main_test.go)):\n```\nimport (\n    \"k8s.io/client-go/tools/clientcmd\"\n    clientset \"k8s.io/redis-operator/pkg/client/clientset/versioned/typed/<some-group>/<some-version>\"\n    ......\n)\n\n// Specify kubeconfig file\nfunc getClientConfig() (*rest.Config, error) {\n    return clientcmd.BuildConfigFromFlags(\"\", path.Join(os.Getenv(\"HOME\"), \"<file-path>\"))\n}\n\n// Set up test environment\nvar _ = Describe(\"<some-controller-name> should work\", func() {\n    config, err := getClientConfig()\n    if err != nil {\n        ......\n    }\n\n    // Construct kubernetes client\n    k8sClient, err := kubernetes.NewForConfig(config)\n    if err != nil {\n        ......\n    }\n\n    // Construct controller client\n    client, err := clientset.NewForConfig(config)\n    if err != nil {\n        ......\n    }\n\n    BeforeEach(func() {\n        // Create environment-specific resources such as controller image StatefulSet,\n        // CRDs etc. Note: refer \"install.yaml\" created via \"kubebuilder create config\"\n        // command to have an idea of what resources to be created.\n        ......\n    })\n\n    AfterEach(func() {\n        // Delete all test-specific resources\n        ......\n\n        // Delete all environment-specific resources\n        ......\n    })\n\n    // Declare a list of testing specifications with corresponding test functions\n    // Note: test-specific resources are normally created within the test functions\n    It(\"should do something\", func() {\n        testDoSomething(k8sClient, roClient)\n    })\n\n    ......\n```\n2.  Write some controller-specific e2e tests\n3.  Build controller image and upload it to an image storage website such as [gcr.io](https://cloud.google.com/container-registry/)\n4.  `go test <path-to-test-file>`\n"
  },
  {
    "path": "docs/testing/integration.md",
    "content": "**Writing and Running Integration Tests**\n\n**This document is for kubebuilder v1 only**\n\nThis article explores steps to write and run integration tests for controllers created using Kubebuilder. Kubebuilder provides a template for writing integration tests. You can simply run all integration (and unit) tests within the project by running: `make test`\n\nFor example, there is a controller watching *Parent* objects. The *Parent* objects create *Child* objects. Note that the *Child* objects must have their `.ownerReferences` field setting to the `Parent` objects. You can find the template under `pkg/controllers/parent/parent_controller_test.go`:\n```\npackage parent\n\nimport (\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/gcp\"\n\tchildapis \"k8s.io/child/pkg/apis\"\n\tchildv1alpha1 \"k8s.io/childrepo/pkg/apis/child/v1alpha1\"\n\tparentapis \"k8s.io/parent/pkg/apis\"\n\tparentv1alpha1 \"k8s.io/parentrepo/pkg/apis/parent/v1alpha1\"\n\n\t...<other import items>...\n)\n\nconst timeout = time.Second * 5\n\nvar c client.Client\nvar expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: \"parent\", Namespace: \"default\"}}\nvar childKey = types.NamespacedName{Name: \"child\", Namespace: \"default\"}\n\nfunc TestReconcile(t *testing.T) {\n\tg := gomega.NewGomegaWithT(t)\n\n\t// Parent instance to be created.\n\tparent := &parentv1alpha1.Parent{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"parent\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: metav1.ParentSpec{\n\t\t\tSomeSpecField:    \"SomeSpecValue\",\n\t\t\tAnotherSpecField: \"AnotherSpecValue\",\n\t\t},\n\t}\n\n\t// Setup the Manager and Controller. Wrap the Controller Reconcile function\n\t// so it writes each request to a channel when it is finished.\n\tmgr, err := manager.New(cfg, manager.Options{})\n\n\t// Setup Scheme for all resources.\n\tif err = parentapis.AddToScheme(mgr.GetScheme()); err != nil {\n\t\tt.Logf(\"failed to add Parent scheme: %v\", err)\n\t}\n\tif err = childapis.AddToScheme(mgr.GetScheme()); err != nil {\n\t\tt.Logf(\"failed to add Child scheme: %v\", err)\n\t}\n\n\t// Set up and start test manager.\n\treconciler, err := newReconciler(mgr)\n\tg.Expect(err).NotTo(gomega.HaveOccurred())\n\trecFn, requests := SetupTestReconcile(reconciler)\n\tg.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred())\n\tdefer close(StartTestManager(mgr, g))\n\n\t// Create the Parent object and expect the Reconcile and Child to be created.\n\tc = mgr.GetClient()\n\terr = c.Create(context.TODO(), parent)\n\tg.Expect(err).NotTo(gomega.HaveOccurred())\n\tdefer c.Delete(context.TODO(), parent)\n\tg.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))\n\n\t// Verify Child is created.\n\tchild := &childv1alpha1.Child{}\n\tg.Eventually(func() error { return c.Get(context.TODO(), childKey, child) }, timeout).\n\t\tShould(gomega.Succeed())\n\n\t// Manually delete Child since GC isn't enabled in the test control plane.\n\tg.Expect(c.Delete(context.TODO(), child)).To(gomega.Succeed())\n}\n```\n\n`SetupTestReconcile` function above brings up an API server and etcd instance. Note that there are no nodes created for the integration testing environment. If you want to test your controller on a real node, you should write end-to-end tests.\n\nThe manager is started as part of the test itself (`StartTestManager` function).\n\nBoth functions are located in `pkg/controllers/parent/parent_controller_suite_test.go` file. The file also contains a `TestMain` function that allows you to specify CRD directory paths for the testing environment.\n"
  },
  {
    "path": "docs/windows.md",
    "content": "# Windows Support\n\nSince no efforts have been made to add support for Windows in the past couple of years, we have decided not to pursue native Windows support at this time, considering both the additional maintenance overhead it adds to the project and the limited community contributions in that area.\n\nThat said, it’s still possible to use and contribute to Kubebuilder on a Windows machine by using WSL2 (Windows Subsystem for Linux) together with Docker Desktop.\n\n- Learn more about setting up WSL2 in the [official docs](https://learn.microsoft.com/en-us/windows/wsl/).\n- The [Docker Desktop documentation](https://docs.docker.com/desktop/features/wsl/) has instructions on how to set up Docker to use WSL2 as the backend on Windows.\n- You can also learn more about setting up kind with Docker on WSL2 in the [kind official documentation](https://kind.sigs.k8s.io/docs/user/using-wsl2/).\n\nAll other dependencies and environment settings can be set up by following the Linux instructions.\n"
  },
  {
    "path": "go.mod",
    "content": "module sigs.k8s.io/kubebuilder/v4\n\ngo 1.25.3\n\nretract v4.10.0 // invalid filename causes go get/install failure (#5211)\n\nrequire (\n\tgithub.com/gobuffalo/flect v1.0.3\n\tgithub.com/h2non/gock v1.2.0\n\tgithub.com/onsi/ginkgo/v2 v2.28.1\n\tgithub.com/onsi/gomega v1.39.1\n\tgithub.com/spf13/afero v1.15.0\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgo.yaml.in/yaml/v3 v3.0.4\n\tgolang.org/x/mod v0.34.0\n\tgolang.org/x/text v0.35.0\n\tgolang.org/x/tools v0.43.0\n\thelm.sh/helm/v3 v3.20.1\n\tk8s.io/apimachinery v0.35.2\n\tsigs.k8s.io/yaml v1.6.0\n)\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect\n\tgithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgolang.org/x/net v0.52.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=\ngithub.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=\ngithub.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=\ngithub.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=\ngithub.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=\ngithub.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=\ngithub.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=\ngithub.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\ngithub.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=\ngithub.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=\ngithub.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=\ngithub.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=\ngithub.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=\ngithub.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=\ngithub.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=\ngithub.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=\ngolang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngolang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=\ngolang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhelm.sh/helm/v3 v3.20.1 h1:T8PodUaH1UwNvE+imUA2mIKjJItY8g7CVvLVP5g4NzI=\nhelm.sh/helm/v3 v3.20.1/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "hack/docs/check.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2023 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ncheck_directory=\"$(dirname \"$0\")/../../docs/book/src/\"\n\n# Check docs directory first. If there are any uncommitted change, fail the test.\nif [[ $(git status ${check_directory} --porcelain) ]]; then\n  echo \"Generate Docs test precondition failed!\"\n  echo \"Please commit the change under docs directory before running the Generate Docs test\"\n  exit 1\nfi\n\n\n$(dirname \"$0\")/generate.sh\n\n# Check if there are any changes to files under testdata directory.\nif [[ $(git status ${check_directory} --porcelain) ]]; then\n  git status ${check_directory} --porcelain\n  git diff ${check_directory}\n  echo \"Generate Docs failed!\"\n  echo \"Please, if you have changed the scaffolding make sure you have run: make generate\"\n  exit 1\nelse\n  echo \"Generate Docs passed!\"\nfi\n"
  },
  {
    "path": "hack/docs/generate.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2023 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../../test/common.sh\"\n\nbuild_kb\n\n# ensure that destroy succeed\nchmod -R +w docs/book/src/cronjob-tutorial/testdata/project/\nchmod -R +w docs/book/src/getting-started/testdata/project/\n\ndocs_gen_directory=\"$(dirname \"$0\")/../../hack/docs/generate_samples.go\"\ngo run ${docs_gen_directory}\n\n\n"
  },
  {
    "path": "hack/docs/generate_samples.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\n\tcronjob \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/cronjob-tutorial\"\n\tgettingstarted \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/getting-started\"\n\tmultiversion \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/multiversion-tutorial\"\n\t\"sigs.k8s.io/kubebuilder/v4/internal/logging\"\n)\n\n// KubebuilderBinName make sure executing `build_kb` to generate kb executable from the source code\nconst KubebuilderBinName = \"/tmp/kubebuilder/bin/kubebuilder\"\n\ntype tutorialGenerator interface {\n\tPrepare()\n\tGenerateSampleProject()\n\tUpdateTutorial()\n\tCodeGen()\n}\n\nfunc main() {\n\ttype generator func()\n\n\ttutorials := map[string]generator{\n\t\t\"cronjob\":         updateCronjobTutorial,\n\t\t\"getting-started\": updateGettingStarted,\n\t\t\"multiversion\":    updateMultiversionTutorial,\n\t}\n\n\topts := logging.HandlerOptions{\n\t\tSlogOpts: slog.HandlerOptions{\n\t\t\tLevel: slog.LevelInfo,\n\t\t},\n\t}\n\thandler := logging.NewHandler(os.Stdout, opts)\n\tlogger := slog.New(handler)\n\tslog.SetDefault(logger)\n\tslog.Info(\"Generating documents...\")\n\n\tfor tutorial, updater := range tutorials {\n\t\tslog.Info(\"Generating tutorial\", \"name\", tutorial)\n\t\tupdater()\n\t}\n}\n\nfunc updateTutorial(generator tutorialGenerator) {\n\tgenerator.Prepare()\n\tgenerator.GenerateSampleProject()\n\tgenerator.UpdateTutorial()\n\tgenerator.CodeGen()\n}\n\nfunc updateCronjobTutorial() {\n\tsamplePath := \"docs/book/src/cronjob-tutorial/testdata/project/\"\n\tsp := cronjob.NewSample(KubebuilderBinName, samplePath)\n\tupdateTutorial(&sp)\n}\n\nfunc updateGettingStarted() {\n\tsamplePath := \"docs/book/src/getting-started/testdata/project\"\n\tsp := gettingstarted.NewSample(KubebuilderBinName, samplePath)\n\tupdateTutorial(&sp)\n}\n\nfunc updateMultiversionTutorial() {\n\tsamplePath := \"docs/book/src/multiversion-tutorial/testdata/project\"\n\tsp := cronjob.NewSample(KubebuilderBinName, samplePath)\n\tupdateTutorial(&sp)\n\tmulti := multiversion.NewSample(KubebuilderBinName, samplePath)\n\tupdateTutorial(&multi)\n}\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/api_design.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst cronjobSpecExplaination = `\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\n First, let's take a look at our spec.  As we discussed before, spec holds\n *desired state*, so any \"inputs\" to our controller go here.\n\n Fundamentally a CronJob needs the following pieces:\n\n - A schedule (the *cron* in CronJob)\n - A template for the Job to run (the\n *job* in CronJob)\n\n We'll also want a few extras, which will make our users' lives easier:\n\n - A deadline for starting jobs (if we miss this deadline, we'll just wait till\n   the next scheduled time)\n - What to do if multiple jobs would run at once (do we wait? stop the old one? run both?)\n - A way to pause the running of a CronJob, in case something's wrong with it\n - Limits on old job history\n\n Remember, since we never read our own status, we need to have some other way to\n keep track of whether a job has run.  We can use at least one old job to do\n this.\n\n We'll use several markers (` + \"`\" + `// +comment` + \"`\" + `) to specify additional metadata.  These\n will be used by [controller-tools](https://github.com/kubernetes-sigs/controller-tools) when generating our CRD manifest.\n As we'll see in a bit, controller-tools will also use GoDoc to form descriptions for\n the fields.\n*/\n`\n\nconst cronjobSpecStruct = `\n\t// schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.\n\t// +kubebuilder:validation:MinLength=0\n\t// +required\n\tSchedule string` + \" `\" + `json:\"schedule\"` + \"`\" + `\n\n\t// startingDeadlineSeconds defines in seconds for starting the job if it misses scheduled\n\t// time for any reason.  Missed jobs executions will be counted as failed ones.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tStartingDeadlineSeconds *int64` + \" `\" + `json:\"startingDeadlineSeconds,omitempty\"` + \"`\" + `\n\n\t// concurrencyPolicy specifies how to treat concurrent executions of a Job.\n\t// Valid values are:\n\t// - \"Allow\" (default): allows CronJobs to run concurrently;\n\t// - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet;\n\t// - \"Replace\": cancels currently running job and replaces it with a new one\n\t// +optional\n\t// +kubebuilder:default:=Allow\n\tConcurrencyPolicy ConcurrencyPolicy` + \" `\" + `json:\"concurrencyPolicy,omitempty\"` + \"`\" + `\n\n\t// suspend tells the controller to suspend subsequent executions, it does\n\t// not apply to already started executions.  Defaults to false.\n\t// +optional\n\tSuspend *bool` + \" `\" + `json:\"suspend,omitempty\"` + \"`\" + `\n\n\t// jobTemplate defines the job that will be created when executing a CronJob.\n\t// +required\n\tJobTemplate batchv1.JobTemplateSpec` + \" `\" + `json:\"jobTemplate\"` + \"`\" + `\n\n\t// successfulJobsHistoryLimit defines the number of successful finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tSuccessfulJobsHistoryLimit *int32` + \" `\" + `json:\"successfulJobsHistoryLimit,omitempty\"` + \"`\" + `\n\n\t// failedJobsHistoryLimit defines the number of failed finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tFailedJobsHistoryLimit *int32` + \" `\" + `json:\"failedJobsHistoryLimit,omitempty\"` + \"`\" + `\n}\n\n/*\n We define a custom type to hold our concurrency policy.  It's actually\n just a string under the hood, but the type gives extra documentation,\n and allows us to attach validation on the type instead of the field,\n making the validation more easily reusable.\n*/\n\n// ConcurrencyPolicy describes how the job will be handled.\n// Only one of the following concurrent policies may be specified.\n// If none of the following policies is specified, the default one\n// is AllowConcurrent.\n// +kubebuilder:validation:Enum=Allow;Forbid;Replace\ntype ConcurrencyPolicy string\n\nconst (\n\t// AllowConcurrent allows CronJobs to run concurrently.\n\tAllowConcurrent ConcurrencyPolicy = \"Allow\"\n\n\t// ForbidConcurrent forbids concurrent runs, skipping next run if previous\n\t// hasn't finished yet.\n\tForbidConcurrent ConcurrencyPolicy = \"Forbid\"\n\n\t// ReplaceConcurrent cancels currently running job and replaces it with a new one.\n\tReplaceConcurrent ConcurrencyPolicy = \"Replace\"\n)\n\n/*\n Next, let's design our status, which holds observed state.  It contains any information\n we want users or other controllers to be able to easily obtain.\n\n We'll keep a list of actively running jobs, as well as the last time that we successfully\n ran our job.  Notice that we use` + \" `\" + `metav1.Time` + \"`\" + ` instead of` + \" `\" + `time.Time` + \"`\" + ` to get the stable\n serialization, as mentioned above.\n*/`\n\nconst cronjobList = `\n\n\t// active defines a list of pointers to currently running jobs.\n\t// +optional\n\t// +listType=atomic\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:MaxItems=10\n\tActive []corev1.ObjectReference` + \" `\" + `json:\"active,omitempty\"` + \"`\" + `\n\n\t// lastScheduleTime defines when was the last time the job was successfully scheduled.\n\t// +optional\n\tLastScheduleTime *metav1.Time` + \" `\" + `json:\"lastScheduleTime,omitempty\"` + \"`\" + `\n`\n\nconst docCommentStatusSub = `\n/*\n Finally, we have the rest of the boilerplate that we've already discussed.\n As previously noted, we don't need to change this, except to mark that\n we want a status subresource, so that we behave like built-in kubernetes types.\n*/\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/controller_implementation.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst controllerIntro = `\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWe'll start out with some imports.  You'll see below that we'll need a few more imports\nthan those scaffolded for us.  We'll talk about each one when we use it.\n*/`\n\nconst controllerImport = `import (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/robfig/cron\"\n\tkbatch \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tref \"k8s.io/client-go/tools/reference\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n/*\nNext, we'll need a Clock, which will allow us to fake timing in our tests.\n*/\n`\n\nconst controllerMockClock = `\n/*\nWe'll mock out the clock to make it easier to jump around in time while testing,\nthe \"real\" clock just calls` + \" `\" + `time.Now` + \"`\" + `.\n*/\ntype realClock struct{}\n\nfunc (_ realClock) Now() time.Time { return time.Now() } //nolint:staticcheck\n\n// Clock knows how to get the current time.\n// It can be used to fake out timing for testing.\ntype Clock interface {\n\tNow() time.Time\n}\n\n// +kubebuilder:docs-gen:collapse=Clock Code Implementation\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableCronJob represents the status of the CronJob reconciliation\n\ttypeAvailableCronJob = \"Available\"\n\t// typeProgressingCronJob represents the status used when the CronJob is being reconciled\n\ttypeProgressingCronJob = \"Progressing\"\n\t// typeDegradedCronJob represents the status used when the CronJob has encountered an error\n\ttypeDegradedCronJob = \"Degraded\"\n)\n\n/*\nNotice that we need a few more RBAC permissions -- since we're creating and\nmanaging jobs now, we'll need permissions for those, which means adding\na couple more [markers](/reference/markers/rbac.md).\n*/\n`\n\nconst controllerReconcile = `\n// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get\n\n/*\nNow, we get to the heart of the controller -- the reconciler logic.\n*/\nvar (\n\tscheduledTimeAnnotation = \"batch.tutorial.kubebuilder.io/scheduled-at\"\n)\n`\n\nconst skipGoCycloLint = `\n// nolint:gocyclo`\n\nconst controllerReconcileLogic = `log := logf.FromContext(ctx)\n\n\t/*\n\t\t### 1: Load the CronJob by name\n\n\t\tWe'll fetch the CronJob using our client.  All client methods take a\n\t\tcontext (to allow for cancellation) as their first argument, and the object\n\t\tin question as their last.  Get is a bit special, in that it takes a\n\t\t[` + \"`\" + `NamespacedName` + \"`\" + `](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/client?tab=doc#ObjectKey)\n\t\tas the middle argument (most don't have a middle argument, as we'll see\n\t\tbelow).\n\n\t\tMany client methods also take variadic options at the end.\n\t*/\n\tvar cronJob batchv1.CronJob\n\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"CronJob resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get CronJob\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Initialize status conditions if not yet present\n\tif len(cronJob.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionUnknown,\n\t\t\tReason:  \"Reconciling\",\n\t\t\tMessage: \"Starting reconciliation\",\n\t\t})\n\t\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to update CronJob status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t/*\n\t\tAfter updating the status, we re-fetch the CronJob to ensure we are working with\n\t\tthe latest version of the object from the API server.\n\t\t\n\t\tKubernetes uses optimistic concurrency, meaning that any update (including a\n\t\tstatus update) may change the resource version. If we continue reconciliation\n\t\twith a stale copy, subsequent updates may fail with a conflict such as:\n\t\t\"the object has been modified; please apply your changes to the latest version and try again\".\n\t\t\n\t\tBy re-fetching here, we keep our reconciliation logic in sync with the actual\n\t\tcluster state and avoid unnecessary conflicts and requeues.\n\t\t*/\n\t\tif err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t/*\n\t\t### 2: List all active jobs, and update the status\n\n\t\tTo fully update our status, we'll need to list all child jobs in this namespace that belong to this CronJob.\n\t\tSimilarly to Get, we can use the List method to list the child jobs.  Notice that we use variadic options to\n\t\tset the namespace and field match (which is actually an index lookup that we set up below).\n\t*/\n\tvar childJobs kbatch.JobList\n\tif err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}); err != nil {\n\t\tlog.Error(err, \"unable to list child Jobs\")\n\t\t/*\n\t\tBefore updating, ensure we have the latest state of the resource to avoid\n\t\tconflict errors (e.g. \"the object has been modified\") that would re-trigger\n\t\tthe reconcile loop.\n\t\t*/\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"ReconciliationError\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to list child jobs: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\n\t\t<aside class=\"note\">\n\n\t\t<h1>What is this index about?</h1>\n\n\t\t<p>The reconciler fetches all jobs owned by the cronjob for the status. As our number of cronjobs increases,\n\t\tlooking these up can become quite slow as we have to filter through all of them. For a more efficient lookup,\n\t\tthese jobs will be indexed locally on the controller's name. A jobOwnerKey field is added to the\n\t\tcached job objects. This key references the owning controller and functions as the index. Later in this\n\t\tdocument we will configure the manager to actually index this field.</p>\n\n\t\t</aside>\n\n\t\tOnce we have all the jobs we own, we'll split them into active, successful,\n\t\tand failed jobs, keeping track of the most recent run so that we can record it\n\t\tin status.  Remember, status should be able to be reconstituted from the state\n\t\tof the world, so it's generally not a good idea to read from the status of the\n\t\troot object.  Instead, you should reconstruct it every run.  That's what we'll\n\t\tdo here.\n\n\t\tWe can check if a job is \"finished\" and whether it succeeded or failed using status\n\t\tconditions.  We'll put that logic in a helper to make our code cleaner.\n\t*/\n\n\t// find the active list of jobs\n\tvar activeJobs []*kbatch.Job\n\tvar successfulJobs []*kbatch.Job\n\tvar failedJobs []*kbatch.Job\n\tvar mostRecentTime *time.Time // find the last run so we can update the status\n\n\t/*\n\t\tWe consider a job \"finished\" if it has a \"Complete\" or \"Failed\" condition marked as true.\n\t\tStatus conditions allow us to add extensible status information to our objects that other\n\t\thumans and controllers can examine to check things like completion and health.\n\t*/\n\tisJobFinished := func(job *kbatch.Job) (bool, kbatch.JobConditionType) {\n\t\tfor _, c := range job.Status.Conditions {\n\t\t\tif (c.Type == kbatch.JobComplete || c.Type == kbatch.JobFailed) && c.Status == corev1.ConditionTrue {\n\t\t\t\treturn true, c.Type\n\t\t\t}\n\t\t}\n\n\t\treturn false, \"\"\n\t}\n\t// +kubebuilder:docs-gen:collapse=isJobFinished\n\n\t/*\n\t\tWe'll use a helper to extract the scheduled time from the annotation that\n\t\twe added during job creation.\n\t*/\n\tgetScheduledTimeForJob := func(job *kbatch.Job) (*time.Time, error) {\n\t\ttimeRaw := job.Annotations[scheduledTimeAnnotation]\n\t\tif len(timeRaw) == 0 {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\ttimeParsed, err := time.Parse(time.RFC3339, timeRaw)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &timeParsed, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getScheduledTimeForJob\n\n\tfor i, job := range childJobs.Items {\n\t\t_, finishedType := isJobFinished(&job)\n\t\tswitch finishedType {\n\t\tcase \"\": // ongoing\n\t\t\tactiveJobs = append(activeJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobFailed:\n\t\t\tfailedJobs = append(failedJobs, &childJobs.Items[i])\n\t\tcase kbatch.JobComplete:\n\t\t\tsuccessfulJobs = append(successfulJobs, &childJobs.Items[i])\n\t\t}\n\n\t\t// We'll store the launch time in an annotation, so we'll reconstitute that from\n\t\t// the active jobs themselves.\n\t\tscheduledTimeForJob, err := getScheduledTimeForJob(&job)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to parse schedule time for child job\", \"job\", &job)\n\t\t\tcontinue\n\t\t}\n\t\tif scheduledTimeForJob != nil {\n\t\t\tif mostRecentTime == nil || mostRecentTime.Before(*scheduledTimeForJob) {\n\t\t\t\tmostRecentTime = scheduledTimeForJob\n\t\t\t}\n\t\t}\n\t}\n\n\tif mostRecentTime != nil {\n\t\tcronJob.Status.LastScheduleTime = &metav1.Time{Time: *mostRecentTime}\n\t} else {\n\t\tcronJob.Status.LastScheduleTime = nil\n\t}\n\tcronJob.Status.Active = nil\n\tfor _, activeJob := range activeJobs {\n\t\tjobRef, err := ref.GetReference(r.Scheme, activeJob)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to make reference to active job\", \"job\", activeJob)\n\t\t\tcontinue\n\t\t}\n\t\tcronJob.Status.Active = append(cronJob.Status.Active, *jobRef)\n\t}\n\n\t/*\n\t\tHere, we'll log how many jobs we observed at a slightly higher logging level,\n\t\tfor debugging.  Notice how instead of using a format string, we use a fixed message,\n\t\tand attach key-value pairs with the extra information.  This makes it easier to\n\t\tfilter and query log lines.\n\t*/\n\tlog.V(1).Info(\"job count\", \"active jobs\", len(activeJobs), \"successful jobs\", len(successfulJobs), \"failed jobs\", len(failedJobs))\n\n\t// Check if CronJob is suspended\n\tisSuspended := cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend\n\n\t// Update status conditions based on current state\n\tif isSuspended {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"Suspended\",\n\t\t\tMessage: \"CronJob is suspended\",\n\t\t})\n\t} else if len(failedJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"JobsFailed\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) have failed\", len(failedJobs)),\n\t\t})\n\t} else if len(activeJobs) > 0 {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"%d job(s) are currently active\", len(activeJobs)),\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobsActive\",\n\t\t\tMessage: fmt.Sprintf(\"CronJob is progressing with %d active job(s)\", len(activeJobs)),\n\t\t})\n\t} else {\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeAvailableCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"AllJobsCompleted\",\n\t\t\tMessage: \"All jobs have completed successfully\",\n\t\t})\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeProgressingCronJob,\n\t\t\tStatus:  metav1.ConditionFalse,\n\t\t\tReason:  \"NoJobsActive\",\n\t\t\tMessage: \"No jobs are currently active\",\n\t\t})\n\t}\n\n\t/*\n\t\tUsing the data we've gathered, we'll update the status of our CRD.\n\t\tJust like before, we use our client.  To specifically update the status\n\t\tsubresource, we'll use the` + \" `\" + `Status` + \"`\" + ` part of the client, with the` + \" `\" + `Update` + \"`\" + `\n\t\tmethod.\n\n\t\tThe status subresource ignores changes to spec, so it's less likely to conflict\n\t\twith any other updates, and can have separate permissions.\n\t*/\n\tif err := r.Status().Update(ctx, &cronJob); err != nil {\n\t\tlog.Error(err, \"unable to update CronJob status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t/*\n\t\tOnce we've updated our status, we can move on to ensuring that the status of\n\t\tthe world matches what we want in our spec.\n\n\t\t### 3: Clean up old jobs according to the history limit\n\n\t\tFirst, we'll try to clean up old jobs, so that we don't leave too many lying\n\t\taround.\n\t*/\n\n\t// NB: deleting these are \"best effort\" -- if we fail on a particular one,\n\t// we won't requeue just to finish the deleting.\n\tif cronJob.Spec.FailedJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(failedJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range failedJobs {\n\t\t\tif int32(i) >= int32(len(failedJobs))-*cronJob.Spec.FailedJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old failed job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old failed job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit != nil {\n\t\tslices.SortStableFunc(successfulJobs, func(a, b *kbatch.Job) int {\n\t\t\taStartTime := a.Status.StartTime\n\t\t\tbStartTime := b.Status.StartTime\n\t\t\tif aStartTime == nil && bStartTime != nil {\n\t\t\t\treturn 1\n\t\t\t}\n\n\t\t\tif aStartTime.Before(bStartTime) {\n\t\t\t\treturn -1\n\t\t\t} else if bStartTime.Before(aStartTime) {\n\t\t\t\treturn 1\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t\tfor i, job := range successfulJobs {\n\t\t\tif int32(i) >= int32(len(successfulJobs))-*cronJob.Spec.SuccessfulJobsHistoryLimit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := r.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); err != nil {\n\t\t\t\tlog.Error(err, \"unable to delete old successful job\", \"job\", job)\n\t\t\t} else {\n\t\t\t\tlog.V(1).Info(\"deleted old successful job\", \"job\", job)\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ### 4: Check if we're suspended\n\n\tIf this object is suspended, we don't want to run any jobs, so we'll stop now.\n\tThis is useful if something's broken with the job we're running and we want to\n\tpause runs to investigate or putz with the cluster, without deleting the object.\n\t*/\n\n\tif cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend {\n\t\tlog.V(1).Info(\"cronjob suspended, skipping\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\t### 5: Get the next scheduled run\n\n\t\tIf we're not paused, we'll need to calculate the next scheduled run, and whether\n\t\tor not we've got a run that we haven't processed yet.\n\t*/\n\n\t/*\n\t\tWe'll calculate the next scheduled time using our helpful cron library.\n\t\tWe'll start calculating appropriate times from our last run, or the creation\n\t\tof the CronJob if we can't find a last run.\n\n\t\tIf there are too many missed runs and we don't have any deadlines set, we'll\n\t\tbail so that we don't cause issues on controller restarts or wedges.\n\n\t\tOtherwise, we'll just return the missed runs (of which we'll just use the latest),\n\t\tand the next run, so that we can know when it's time to reconcile again.\n\t*/\n\tgetNextSchedule := func(cronJob *batchv1.CronJob, now time.Time) (lastMissed time.Time, next time.Time, err error) {\n\t\tsched, err := cron.ParseStandard(cronJob.Spec.Schedule)\n\t\tif err != nil {\n\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"unparseable schedule %q: %w\", cronJob.Spec.Schedule, err)\n\t\t}\n\n\t\t// for optimization purposes, cheat a bit and start from our last observed run time\n\t\t// we could reconstitute this here, but there's not much point, since we've\n\t\t// just updated it.\n\t\tvar earliestTime time.Time\n\t\tif cronJob.Status.LastScheduleTime != nil {\n\t\t\tearliestTime = cronJob.Status.LastScheduleTime.Time\n\t\t} else {\n\t\t\tearliestTime = cronJob.CreationTimestamp.Time\n\t\t}\n\t\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\t\t// controller is not going to schedule anything below this point\n\t\t\tschedulingDeadline := now.Add(-time.Second * time.Duration(*cronJob.Spec.StartingDeadlineSeconds))\n\n\t\t\tif schedulingDeadline.After(earliestTime) {\n\t\t\t\tearliestTime = schedulingDeadline\n\t\t\t}\n\t\t}\n\t\tif earliestTime.After(now) {\n\t\t\treturn time.Time{}, sched.Next(now), nil\n\t\t}\n\n\t\tstarts := 0\n\t\tfor t := sched.Next(earliestTime); !t.After(now); t = sched.Next(t) {\n\t\t\tlastMissed = t\n\t\t\t// An object might miss several starts. For example, if\n\t\t\t// controller gets wedged on Friday at 5:01pm when everyone has\n\t\t\t// gone home, and someone comes in on Tuesday AM and discovers\n\t\t\t// the problem and restarts the controller, then all the hourly\n\t\t\t// jobs, more than 80 of them for one hourly scheduledJob, should\n\t\t\t// all start running with no further intervention (if the scheduledJob\n\t\t\t// allows concurrency and late starts).\n\t\t\t//\n\t\t\t// However, if there is a bug somewhere, or incorrect clock\n\t\t\t// on controller's server or apiservers (for setting creationTimestamp)\n\t\t\t// then there could be so many missed start times (it could be off\n\t\t\t// by decades or more), that it would eat up all the CPU and memory\n\t\t\t// of this controller. In that case, we want to not try to list\n\t\t\t// all the missed start times.\n\t\t\tstarts++\n\t\t\tif starts > 100 {\n\t\t\t\t// We can't get the most recent times so just return an empty slice\n\t\t\t\treturn time.Time{}, time.Time{}, fmt.Errorf(\"Too many missed start times (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.\") //nolint:staticcheck\n\t\t\t}\n\t\t}\n\t\treturn lastMissed, sched.Next(now), nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=getNextSchedule\n\n\t// figure out the next times that we need to create\n\t// jobs at (or anything we missed).\n\tmissedRun, nextRun, err := getNextSchedule(&cronJob, r.Now())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to figure out CronJob schedule\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the schedule error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"InvalidSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to parse schedule: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\t// we don't really care about requeuing until we get an update that\n\t\t// fixes the schedule, so don't return an error\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t/*\n\t\tWe'll prep our eventual request to requeue until the next job, and then figure\n\t\tout if we actually need to run.\n\t*/\n\tscheduledResult := ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())} // save this so we can re-use it elsewhere\n\tlog = log.WithValues(\"now\", r.Now(), \"next run\", nextRun)\n\n\t/*\n\t\t### 6: Run a new job if it's on schedule, not past the deadline, and not blocked by our concurrency policy\n\n\t\tIf we've missed a run, and we're still within the deadline to start it, we'll need to run a job.\n\t*/\n\tif missedRun.IsZero() {\n\t\tlog.V(1).Info(\"no upcoming scheduled times, sleeping until next\")\n\t\treturn scheduledResult, nil\n\t}\n\n\t// make sure we're not too late to start the run\n\tlog = log.WithValues(\"current run\", missedRun)\n\ttooLate := false\n\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\ttooLate = missedRun.Add(time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second).Before(r.Now())\n\t}\n\tif tooLate {\n\t\tlog.V(1).Info(\"missed starting deadline for last run, sleeping till next\")\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect missed deadline\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"MissedSchedule\",\n\t\t\tMessage: fmt.Sprintf(\"Missed starting deadline for run at %v\", missedRun),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn scheduledResult, nil\n\t}\n\n\t/*\n\t\tIf we actually have to run a job, we'll need to either wait till existing ones finish,\n\t\treplace the existing ones, or just add new ones.  If our information is out of date due\n\t\tto cache delay, we'll get a requeue when we get up-to-date information.\n\t*/\n\t// figure out how to run this job -- concurrency policy might forbid us from running\n\t// multiple at the same time...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ForbidConcurrent && len(activeJobs) > 0 {\n\t\tlog.V(1).Info(\"concurrency policy blocks concurrent runs, skipping\", \"num active\", len(activeJobs))\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...or instruct us to replace existing ones...\n\tif cronJob.Spec.ConcurrencyPolicy == batchv1.ReplaceConcurrent {\n\t\tfor _, activeJob := range activeJobs {\n\t\t\t// we don't care if the job was already deleted\n\t\t\tif err := r.Delete(ctx, activeJob, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil {\n\t\t\t\tlog.Error(err, \"unable to delete active job\", \"job\", activeJob)\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t\tOnce we've figured out what to do with existing jobs, we'll actually create our desired job\n\t*/\n\n\t/*\n\tWe need to construct a job based on our CronJob's template.  We'll copy over the spec\n\tfrom the template and copy some basic object meta.\n\n\tThen, we'll set the \"scheduled time\" annotation so that we can reconstitute our\n\t` + \"`\" + `LastScheduleTime` + \"`\" + ` field each reconcile.\n\n\tFinally, we'll need to set an owner reference.  This allows the Kubernetes garbage collector\n\tto clean up jobs when we delete the CronJob, and allows controller-runtime to figure out\n\twhich cronjob needs to be reconciled when a given job changes (is added, deleted, completes, etc).\n\t*/\n\tconstructJobForCronJob := func(cronJob *batchv1.CronJob, scheduledTime time.Time) (*kbatch.Job, error) {\n\t\t// We want job names for a given nominal start time to have a deterministic name to avoid the same job being created twice\n\t\tname := fmt.Sprintf(\"%s-%d\", cronJob.Name, scheduledTime.Unix())\n\n\t\tjob := &kbatch.Job{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tLabels:      make(map[string]string),\n\t\t\t\tAnnotations: make(map[string]string),\n\t\t\t\tName:        name,\n\t\t\t\tNamespace:   cronJob.Namespace,\n\t\t\t},\n\t\t\tSpec: *cronJob.Spec.JobTemplate.Spec.DeepCopy(),\n\t\t}\n\t\tmaps.Copy(job.Annotations, cronJob.Spec.JobTemplate.Annotations)\n\t\tjob.Annotations[scheduledTimeAnnotation] = scheduledTime.Format(time.RFC3339)\n\t\tmaps.Copy(job.Labels, cronJob.Spec.JobTemplate.Labels)\n\t\tif err := ctrl.SetControllerReference(cronJob, job, r.Scheme); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn job, nil\n\t}\n\t// +kubebuilder:docs-gen:collapse=constructJobForCronJob\n\n\t// actually make the job...\n\tjob, err := constructJobForCronJob(&cronJob, missedRun)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to construct job from template\")\n\t\t// don't bother requeuing until we get a change to the spec\n\t\treturn scheduledResult, nil\n\t}\n\n\t// ...and create it on the cluster\n\tif err := r.Create(ctx, job); err != nil {\n\t\tlog.Error(err, \"unable to create Job for CronJob\", \"job\", job)\n\t\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\t\treturn ctrl.Result{}, fetchErr\n\t\t}\n\t\t// Update status condition to reflect the error\n\t\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\t\tType:    typeDegradedCronJob,\n\t\t\tStatus:  metav1.ConditionTrue,\n\t\t\tReason:  \"JobCreationFailed\",\n\t\t\tMessage: fmt.Sprintf(\"Failed to create job: %v\", err),\n\t\t})\n\t\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t\t}\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tlog.V(1).Info(\"created Job for CronJob run\", \"job\", job)\n\n\tif fetchErr := r.Get(ctx, req.NamespacedName, &cronJob); fetchErr != nil {\n\t\tlog.Error(fetchErr, \"Failed to re-fetch CronJob\")\n\t\treturn ctrl.Result{}, fetchErr\n\t}\n\t// Update status condition to reflect successful job creation\n\tmeta.SetStatusCondition(&cronJob.Status.Conditions, metav1.Condition{\n\t\tType:    typeProgressingCronJob,\n\t\tStatus:  metav1.ConditionTrue,\n\t\tReason:  \"JobCreated\",\n\t\tMessage: fmt.Sprintf(\"Created job %s\", job.Name),\n\t})\n\tif statusErr := r.Status().Update(ctx, &cronJob); statusErr != nil {\n\t\tlog.Error(statusErr, \"Failed to update CronJob status\")\n\t}\n\n\t/*\n\t\t### 7: Requeue when we either see a running job or it's time for the next scheduled run\n\n\t\tFinally, we'll return the result that we prepped above, that says we want to requeue\n\t\twhen our next run would need to occur.  This is taken as a maximum deadline -- if something\n\t\telse changes in between, like our job starts or finishes, we get modified, etc, we might\n\t\treconcile again sooner.\n\t*/\n\t// we'll requeue once we see the running job, and update our status\n\treturn scheduledResult, nil\n}\n\n/*\n### Setup\n\nFinally, we'll update our setup.  In order to allow our reconciler to quickly\nlook up Jobs by their owner, we'll need an index.  We declare an index key that\nwe can later use with the client as a pseudo-field name, and then describe how to\nextract the indexed value from the Job object.  The indexer will automatically take\ncare of namespaces for us, so we just have to extract the owner name if the Job has\na CronJob owner.\n\nAdditionally, we'll inform the manager that this controller owns some Jobs, so that it\nwill automatically call Reconcile on the underlying CronJob when a Job changes, is\ndeleted, etc.\n*/\nvar (\n\tjobOwnerKey = \".metadata.controller\"\n\tapiGVStr    = batchv1.GroupVersion.String()\n)\n`\n\nconst controllerSetupWithManager = `\n\t// set up a real clock, since we're not in a test\n\tif r.Clock == nil {\n\t\tr.Clock = realClock{}\n\t}\n\n\tif err := mgr.GetFieldIndexer().IndexField(context.Background(), &kbatch.Job{}, jobOwnerKey, func(rawObj client.Object) []string {\n\t\t// grab the job object, extract the owner...\n\t\tjob := rawObj.(*kbatch.Job)\n\t\towner := metav1.GetControllerOf(job)\n\t\tif owner == nil {\n\t\t\treturn nil\n\t\t}\n\t\t// ...make sure it's a CronJob...\n\t\tif owner.APIVersion != apiGVStr || owner.Kind != \"CronJob\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// ...and if so, return it\n\t\treturn []string{owner.Name}\n\t}); err != nil {\n\t\treturn err\n\t}\n`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/e2e_implementation.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst isPrometheusInstalledVar = `\n\t// shouldCleanupPrometheus tracks whether Prometheus was installed by this suite.\n\tshouldCleanupPrometheus = false`\n\nconst beforeSuitePrometheus = `\n\tBy(\"Ensure that Prometheus is enabled\")\n\t_ = utils.UncommentCode(\"config/default/kustomization.yaml\", \"#- ../prometheus\", \"#\")\n`\n\nconst afterSuitePrometheus = `\n\t// Teardown Prometheus if it was installed by this suite\n\tif shouldCleanupPrometheus {\n\t\tBy(\"uninstalling Prometheus Operator\")\n\t\tutils.UninstallPrometheusOperator()\n\t}\n`\n\nconst checkPrometheusInstalled = `\n\tBy(\"checking if Prometheus is already installed\")\n\tif !utils.IsPrometheusCRDsInstalled() {\n\t\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\t\tshouldCleanupPrometheus = true\n\n\t\tBy(\"installing Prometheus Operator\")\n\t\tExpect(utils.InstallPrometheusOperator()).To(Succeed(), \"Failed to install Prometheus Operator\")\n\t}\n`\n\nconst serviceMonitorE2e = `\n\n\t\t\tBy(\"validating that the ServiceMonitor for Prometheus is applied in the namespace\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"ServiceMonitor\", \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"ServiceMonitor should exist\")`\n\nconst prometheusUtilities = `// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.\nfunc InstallPrometheusOperator() error {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"create\", \"-f\", url)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// UninstallPrometheusOperator uninstalls the prometheus\nfunc UninstallPrometheusOperator() {\n\turl := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed\n// by verifying the existence of key CRDs related to Prometheus.\nfunc IsPrometheusCRDsInstalled() bool {\n\t// List of common Prometheus CRDs\n\tprometheusCRDs := []string{\n\t\t\"prometheuses.monitoring.coreos.com\",\n\t\t\"prometheusrules.monitoring.coreos.com\",\n\t\t\"prometheusagents.monitoring.coreos.com\",\n\t}\n\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"custom-columns=NAME:.metadata.name\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range prometheusCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n`\n\nconst prometheusVersionURL = `\n\n\tprometheusOperatorVersion = \"v0.89.0\"\n\tprometheusOperatorURL     = \"https://github.com/prometheus-operator/prometheus-operator/\" +\n\t\t\"releases/download/%s/bundle.yaml\"`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/generate_cronjob.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/afero\"\n\n\thackutils \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/utils\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Sample define the sample which will be scaffolded\ntype Sample struct {\n\tctx *utils.TestContext\n}\n\n// NewSample create a new instance of the cronjob sample and configure the KB CLI that will be used\nfunc NewSample(binaryPath, samplePath string) Sample {\n\tlog.Info(\"Generating the sample context of Cronjob...\")\n\tctx := hackutils.NewSampleContext(binaryPath, samplePath, \"GO111MODULE=on\")\n\treturn Sample{&ctx}\n}\n\n// Prepare the Context for the sample project\nfunc (sp *Sample) Prepare() {\n\tlog.Info(\"destroying directory for cronjob sample project\")\n\tsp.ctx.Destroy()\n\n\tlog.Info(\"refreshing tools and creating directory...\")\n\terr := sp.ctx.Prepare()\n\n\thackutils.CheckError(\"creating directory for sample project\", err)\n}\n\n// GenerateSampleProject will generate the sample\nfunc (sp *Sample) GenerateSampleProject() {\n\tlog.Info(\"Initializing the cronjob project\")\n\n\terr := sp.ctx.Init(\n\t\t\"--domain\", \"tutorial.kubebuilder.io\",\n\t\t\"--repo\", \"tutorial.kubebuilder.io/project\",\n\t\t\"--license\", \"apache2\",\n\t\t\"--owner\", \"The Kubernetes authors\",\n\t)\n\thackutils.CheckError(\"Initializing the cronjob project\", err)\n\n\tlog.Info(\"Adding a new config type\")\n\terr = sp.ctx.CreateAPI(\n\t\t\"--group\", \"batch\",\n\t\t\"--version\", \"v1\",\n\t\t\"--kind\", \"CronJob\",\n\t\t\"--resource\", \"--controller\",\n\t)\n\thackutils.CheckError(\"Creating the API\", err)\n\n\tlog.Info(\"Implementing admission webhook\")\n\terr = sp.ctx.CreateWebhook(\n\t\t\"--group\", \"batch\",\n\t\t\"--version\", \"v1\",\n\t\t\"--kind\", \"CronJob\",\n\t\t\"--defaulting\", \"--programmatic-validation\",\n\t)\n\thackutils.CheckError(\"Implementing admission webhook\", err)\n}\n\n// UpdateTutorial the cronjob tutorial with the scaffold changes\nfunc (sp *Sample) UpdateTutorial() {\n\tlog.Info(\"Update tutorial with cronjob code\")\n\t// 1. update specs\n\tsp.updateSpec()\n\t// 2. update webhook\n\tsp.updateWebhook()\n\t// 3. update webhookTests\n\tsp.updateWebhookTests()\n\t// 4. update makefile\n\tsp.updateMakefile()\n\t// 5. generate extra files\n\tcmd := exec.Command(\"go\", \"mod\", \"tidy\")\n\t_, err := sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run go mod tidy for cronjob tutorial\", err)\n\n\tcmd = exec.Command(\"go\", \"get\", \"github.com/robfig/cron\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to get package robfig/cron\", err)\n\n\tcmd = exec.Command(\"make\", \"generate\", \"manifests\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"run make generate and manifests\", err)\n\n\t// 6. compensate other intro in API\n\tsp.updateAPIStuff()\n\t// 7. update reconciliation and main.go\n\t// 7.1 update controller\n\tsp.updateController()\n\t// 7.2 update main.go\n\tsp.updateMain()\n\t// 8. generate extra files\n\tcmd = exec.Command(\"make\", \"generate\", \"manifests\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"run make generate and manifests\", err)\n\t// 9. update suite_test explanation\n\tsp.updateSuiteTest()\n\t// 10. uncomment kustomization\n\tsp.updateKustomization()\n\t// 11. add example\n\tsp.updateExample()\n\t// 12. add test\n\tsp.addControllerTest()\n\t// 13. update e2e tests\n\tsp.updateE2E()\n}\n\n// CodeGen is a noop for this sample, just to make generation of all samples\n// more efficient. We may want to refactor `UpdateTutorial` some day to take\n// advantage of a separate call, but it is not necessary.\nfunc (sp *Sample) CodeGen() {\n\tcmd := exec.Command(\"make\", \"all\")\n\t_, err := sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make all for cronjob tutorial\", err)\n\n\tcmd = exec.Command(\"make\", \"build-installer\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make build-installer for cronjob tutorial\", err)\n\n\terr = sp.ctx.EditHelmPlugin()\n\thackutils.CheckError(\"Failed to enable helm plugin\", err)\n}\n\n// insert code to fix docs\nfunc (sp *Sample) updateSpec() {\n\tvar err error\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`limitations under the License.\n*/`,\n\t\t`\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\n */`)\n\thackutils.CheckError(\"fixing collapse for cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`package v1`,\n\t\t`\n/*\n */`)\n\thackutils.CheckError(\"fixing package for cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`import (`,\n\t\t`\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"`)\n\thackutils.CheckError(\"fixing imports for cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`to be serialized.`, cronjobSpecExplaination)\n\thackutils.CheckError(\"fixing spec explanation for cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`type CronJobSpec struct {`, cronjobSpecStruct)\n\thackutils.CheckError(\"fixing spec struct for cronjob_types.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of CronJob. Edit cronjob_types.go to remove/update\n\t// +optional\n\tFoo *string`+\" `\"+`json:\"foo,omitempty\"`+\"`\", \"\")\n\thackutils.CheckError(\"fixing additional spec fields for cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`// Important: Run \"make\" to regenerate code after modifying this file`, cronjobList)\n\thackutils.CheckError(\"fixing cronjob_types.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status`, docCommentStatusSub)\n\thackutils.CheckError(\"fixing cronjob_types.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`SchemeBuilder.Register(&CronJob{}, &CronJobList{})\n}`, `\n// +kubebuilder:docs-gen:collapse=Root Object Definitions`)\n\thackutils.CheckError(\"fixing builder for cronjob_types.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`// CronJob is the Schema for the cronjobs API\ntype CronJob struct {`, `// CronJob is the Schema for the cronjobs API\ntype CronJob struct {`+`\n\t/*\n\t */`)\n\thackutils.CheckError(\"fixing schema for cronjob_types.go\", err)\n\n\t// fix lint\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`/\n\t\n}`, \"/\")\n\thackutils.CheckError(\"fixing cronjob_types.go end of status\", err)\n}\n\nfunc (sp *Sample) updateAPIStuff() {\n\tvar err error\n\t// fix groupversion_info\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/groupversion_info.go\"),\n\t\t`limitations under the License.\n*/`, groupVersionIntro)\n\thackutils.CheckError(\"fixing groupversion_info.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/groupversion_info.go\"),\n\t\t`\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)`, groupVersionSchema)\n\thackutils.CheckError(\"fixing groupversion_info.go\", err)\n}\n\nfunc (sp *Sample) updateController() {\n\tvar err error\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`limitations under the License.\n*/`, controllerIntro)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`import (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tbatchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)`, controllerImport)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`Scheme *runtime.Scheme`, `\n\tClock`)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`\tClock\n}`, controllerMockClock)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`// +kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update`, controllerReconcile)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\tfmt.Sprintf(`// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@%s/pkg/reconcile`,\n\t\t\tscaffolds.ControllerRuntimeVersion), skipGoCycloLint)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}`, controllerReconcileLogic)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`SetupWithManager(mgr ctrl.Manager) error {`, controllerSetupWithManager)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller.go\"),\n\t\t`For(&batchv1.CronJob{}).`, `\n\t\tOwns(&kbatch.Job{}).`)\n\thackutils.CheckError(\"fixing cronjob_controller.go\", err)\n}\n\nfunc (sp *Sample) updateMain() {\n\tvar err error\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`limitations under the License.\n*/`,\n\t\t`\n// +kubebuilder:docs-gen:collapse=Apache License`)\n\thackutils.CheckError(\"fixing main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`// +kubebuilder:scaffold:imports\n)`, mainBatch)\n\thackutils.CheckError(\"fixing main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`// +kubebuilder:scaffold:scheme\n}`, `\n/*\nThe other thing that's changed is that kubebuilder has added a block calling our\nCronJob controller's`+\" `\"+`SetupWithManager`+\"`\"+` method.\n*/`)\n\thackutils.CheckError(\"fixing main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`func main() {`, `\n\t/*\n\t */`)\n\thackutils.CheckError(\"fixing main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`if err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}`, `\n\n\t// +kubebuilder:docs-gen:collapse=Remaining code from main.go`)\n\thackutils.CheckError(\"fixing main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"cmd/main.go\"),\n\t\t`setupLog.Error(err, \"Failed to create controller\", \"controller\", \"CronJob\")\n\t\tos.Exit(1)\n\t}`, mainEnableWebhook)\n\thackutils.CheckError(\"fixing main.go\", err)\n}\n\nfunc (sp *Sample) updateMakefile() {\n\tconst originalManifestTarget = `.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n`\n\tconst changedManifestTarget = `.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t# Note that the option maxDescLen=0 was added in the default scaffold in order to sort out the issue\n\t# Too long: must have at most 262144 bytes. By using kubectl apply to create / update resources an annotation\n\t# is created by K8s API to store the latest version of the resource ( kubectl.kubernetes.io/last-applied-configuration).\n\t# However, it has a size limit and if the CRD is too big with so many long descriptions as this one it will cause the failure.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd:maxDescLen=0 webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n`\n\terr := pluginutil.ReplaceInFile(filepath.Join(sp.ctx.Dir, \"Makefile\"), originalManifestTarget, changedManifestTarget)\n\thackutils.CheckError(\"updating makefile to use maxDescLen=0 in make manifest target\", err)\n}\n\nfunc (sp *Sample) updateWebhookTests() {\n\tfile := filepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook_test.go\")\n\n\terr := pluginutil.InsertCode(file,\n\t\t`// TODO (user): Add any additional imports if needed`,\n\t\t`\n\t\"k8s.io/utils/ptr\"`)\n\thackutils.CheckError(\"add import for webhook tests\", err)\n\n\terr = pluginutil.ReplaceInFile(file,\n\t\twebhookTestCreateDefaultingFragment,\n\t\twebhookTestCreateDefaultingReplaceFragment)\n\thackutils.CheckError(\"replace create defaulting test\", err)\n\n\terr = pluginutil.ReplaceInFile(file,\n\t\twebhookTestingValidatingTodoFragment,\n\t\twebhookTestingValidatingExampleFragment)\n\thackutils.CheckError(\"replace validating defaulting test\", err)\n\n\terr = pluginutil.ReplaceInFile(file,\n\t\twebhookTestsVars,\n\t\twebhookTestsConstants)\n\thackutils.CheckError(\"replace before each webhook test \", err)\n\n\terr = pluginutil.ReplaceInFile(file,\n\t\twebhookTestsBeforeEachOriginal,\n\t\twebhookTestsBeforeEachChanged)\n\thackutils.CheckError(\"replace before each webhook test \", err)\n}\n\nfunc (sp *Sample) updateWebhook() {\n\tvar err error\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`limitations under the License.\n*/`,\n\t\t`\n// +kubebuilder:docs-gen:collapse=Apache License`)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by adding collapse\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`import (\n\t\"context\"`,\n\t\t`\n\t\n\t\"github.com/robfig/cron\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tvalidationutils \"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"`,\n\t)\n\thackutils.CheckError(\"add extra imports to cronjob_webhook.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`batchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\n`, webhookIntro)\n\thackutils.CheckError(\"fixing cronjob_webhook.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`var cronjoblog = logf.Log.WithName(\"cronjob-resource\")`,\n\t\t`\n/*\nThen, we set up the webhook with the manager.\n*/`)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by setting webhook with manager comment\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!`, webhooksNoticeMarker)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by replacing note about path attribute for webhook notice \", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.`, explanationValidateCRD)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by replacing note about path attribute for explanation validate CRD\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.`, \"\")\n\thackutils.CheckError(\"fixing cronjob_webhook.go by replace TODO to change verbs\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): Add more fields as needed for defaulting`, fragmentForDefaultFields)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by replacing TODO in Defaulter\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`WithDefaulter(&CronJobCustomDefaulter{}).`,\n\t\t`WithDefaulter(&CronJobCustomDefaulter{\n        DefaultConcurrencyPolicy:      batchv1.AllowConcurrent,\n        DefaultSuspend:                false,\n        DefaultSuccessfulJobsHistoryLimit: 3,\n        DefaultFailedJobsHistoryLimit: 1,\n    }).`)\n\thackutils.CheckError(\"replacing WithDefaulter call in cronjob_webhook.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}`, webhookDefaultingSettings)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by adding logic\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil`,\n\t\t`return nil, validateCronJob(obj)`)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by fill in your validation\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil`,\n\t\t`return nil, validateCronJob(newObj)`)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by adding validation logic upon object update\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.`,\n\t\tcustomInterfaceDefaultInfo)\n\thackutils.CheckError(\"fixing cronjob_webhook.go by adding validation logic upon object update\", err)\n\n\terr = pluginutil.AppendCodeAtTheEnd(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\twebhookValidateSpecMethods)\n\thackutils.CheckError(\"adding validation spec methods at the end\", err)\n}\n\nfunc (sp *Sample) updateSuiteTest() {\n\tvar err error\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`limitations under the License.\n*/`, suiteTestIntro)\n\thackutils.CheckError(\"updating suite_test.go to add license intro\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\n\t\"time\"\n`, `\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n`)\n\thackutils.CheckError(\"updating suite_test.go to add ctrl import\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n`, suiteTestEnv)\n\thackutils.CheckError(\"updating suite_test.go to add more variables\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n`, suiteTestAddSchema)\n\thackutils.CheckError(\"updating suite_test.go to add schema\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}`, `\n\t/*\n\t\tThen, we start the envtest cluster.\n\t*/`)\n\thackutils.CheckError(\"updating suite_test.go to add text to show where envtest cluster start\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n`, suiteTestClient)\n\thackutils.CheckError(\"updating suite_test.go to add text about test client\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n`, suiteTestDescription)\n\thackutils.CheckError(\"updating suite_test.go for test description\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/controller/suite_test.go\"),\n\t\t`\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n`, suiteTestCleanup)\n\thackutils.CheckError(\"updating suite_test.go to cleanup tests\", err)\n}\n\nfunc (sp *Sample) updateKustomization() {\n\tvar err error\n\terr = pluginutil.UncommentCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t`#- ../prometheus`, `#`)\n\thackutils.CheckError(\"fixing default/kustomization\", err)\n\n\terr = pluginutil.UncommentCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t`#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment`, `#`)\n\thackutils.CheckError(\"enabling cert_metrics_manager_patch.yaml\", err)\n\n\terr = pluginutil.UncommentCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/prometheus/kustomization.yaml\"),\n\t\t`#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor`, `#`)\n\thackutils.CheckError(\"enabling monitor tls patch\", err)\n\n\terr = pluginutil.UncommentCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\tcertManagerForMetrics, `#`)\n\thackutils.CheckError(\"fixing default/kustomization\", err)\n\n\t// Add ANCHOR markers for webhook documentation sections\n\t// This allows the docs to include only webhook-related parts of the kustomization.yaml\n\n\t// Add anchor for webhook resources section\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t`# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager`,\n\t\t`# ANCHOR: webhook-resources\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# ANCHOR_END: webhook-resources`)\n\thackutils.CheckError(\"adding webhook-resources anchors\", err)\n\n\t// Add anchor for webhook patch section\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t`# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment`,\n\t\t`# ANCHOR: webhook-patch\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n# ANCHOR_END: webhook-patch`)\n\thackutils.CheckError(\"adding webhook-patch anchors\", err)\n\n\t// Add anchor for webhook replacements section (from webhook service to MutatingWebhookConfiguration)\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t` - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service`,\n\t\t` # ANCHOR: webhook-replacements\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service`)\n\thackutils.CheckError(\"adding webhook-replacements anchor start\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/default/kustomization.yaml\"),\n\t\t`        create: true\n\n# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)`,\n\t\t`        create: true\n# ANCHOR_END: webhook-replacements\n\n# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)`)\n\thackutils.CheckError(\"adding webhook-replacements anchor end\", err)\n}\n\nfunc (sp *Sample) updateExample() {\n\tvar err error\n\n\t// samples/batch_v1_cronjob\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/samples/batch_v1_cronjob.yaml\"),\n\t\t`spec:`, cronjobSample)\n\thackutils.CheckError(\"fixing samples/batch_v1_cronjob.yaml\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"config/samples/batch_v1_cronjob.yaml\"),\n\t\t`# TODO(user): Add fields here`, \"\")\n\thackutils.CheckError(\"fixing samples/batch_v1_cronjob.yaml\", err)\n}\n\nfunc (sp *Sample) addControllerTest() {\n\tfs := afero.NewOsFs()\n\terr := afero.WriteFile(fs, filepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller_test.go\"), []byte(controllerTest), 0o600)\n\thackutils.CheckError(\"adding cronjob_controller_test\", err)\n}\n\nfunc (sp *Sample) updateE2E() {\n\tcronjobE2ESuite := filepath.Join(sp.ctx.Dir, \"test\", \"e2e\", \"e2e_suite_test.go\")\n\tcronjobE2ETest := filepath.Join(sp.ctx.Dir, \"test\", \"e2e\", \"e2e_test.go\")\n\tcronjobE2EUtils := filepath.Join(sp.ctx.Dir, \"test\", \"utils\", \"utils.go\")\n\tvar err error\n\n\terr = pluginutil.InsertCode(cronjobE2ESuite, `shouldCleanupCertManager = false`, isPrometheusInstalledVar)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding isPrometheusInstalledVar\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2ESuite, `var _ = BeforeSuite(func() {`, beforeSuitePrometheus)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding prometheus code in the before suite\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2ESuite,\n\t\t`setupCertManager()`,\n\t\tcheckPrometheusInstalled)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding code check if has prometheus\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2EUtils,\n\t\t`defaultKindCluster = \"kind\"`,\n\t\tprometheusVersionURL)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding prometheus version and URL\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2EUtils,\n\t\t`return false\n}\n`,\n\t\tprometheusUtilities)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding prometheus version and URL\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2ESuite, `var _ = AfterSuite(func() {`, afterSuitePrometheus)\n\thackutils.CheckError(\"fixing test/e2e/e2e_suite_test.go by adding prometheus code after suite\", err)\n\n\terr = pluginutil.InsertCode(cronjobE2ETest, `Expect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")`,\n\t\tserviceMonitorE2e)\n\thackutils.CheckError(\"fixing test/e2e/e2e_test.go by adding ServiceMonitor should exist\", err)\n}\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/main_revisited.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst mainBatch = `\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nThe first difference to notice is that kubebuilder has added the new API\ngroup's package (` + \"`\" + `batchv1` + \"`\" + `) to our scheme.  This means that we can use those\nobjects in our controller.\n\nIf we would be using any other CRD we would have to add their scheme the same way.\nBuiltin types such as Job have their scheme added by` + \" `\" + `clientgoscheme` + \"`\" + `.\n*/`\n\nconst mainEnableWebhook = `\n\n\t/*\n\t\tWe'll also set up webhooks for our type, which we'll talk about next.\n\t\tWe just need to add them to the manager.  Since we might want to run\n\t\tthe webhooks separately, or not run them when testing our controller\n\t\tlocally, we'll put them behind an environment variable.\n\n\t\tWe'll just make sure to set` + \" `\" + `ENABLE_WEBHOOKS=false` + \"`\" + ` when we run locally.\n\t*/`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/other_api_files.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst groupVersionIntro = `\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nFirst, we have some *package-level* markers that denote that there are\nKubernetes objects in this package, and that this package represents the group\n` + \"`\" + `batch.tutorial.kubebuilder.io` + \"`\" + `. The` + \" `\" + `object` + \"`\" + ` generator makes use of the\nformer, while the latter is used by the CRD generator to generate the right\nmetadata for the CRDs it creates from this package.\n*/\n`\n\nconst groupVersionSchema = `\n/*\nThen, we have the commonly useful variables that help us set up our Scheme.\nSince we need to use all the types in this package in our controller, it's\nhelpful (and the convention) to have a convenient method to add all the types to\nsome other` + \" `\" + `Scheme` + \"`\" + `. SchemeBuilder makes this easy for us.\n*/`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/sample.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst cronjobSample = `\n  schedule: \"*/1 * * * *\"\n  startingDeadlineSeconds: 60\n  concurrencyPolicy: Allow # explicitly specify, but Allow is also default.\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          securityContext:\n            runAsNonRoot: true\n            runAsUser: 1000\n            seccompProfile:\n              type: RuntimeDefault\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            securityContext:\n              allowPrivilegeEscalation: false\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: false\n          restartPolicy: OnFailure`\n\nconst certManagerForMetrics = `# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/webhook_implementation.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst webhookIntro = `batchv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNext, we'll setup a logger for the webhooks.\n*/\n\n`\n\nconst webhookDefaultingSettings = `// Set default values\n\td.applyDefaults(obj)\n\treturn nil\n}\n\n// applyDefaults applies default values to CronJob fields.\nfunc (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv1.CronJob) {\n\tif cronJob.Spec.ConcurrencyPolicy == \"\" {\n\t\tcronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy\n\t}\n\tif cronJob.Spec.Suspend == nil {\n\t\tcronJob.Spec.Suspend = new(bool)\n\t\t*cronJob.Spec.Suspend = d.DefaultSuspend\n\t}\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit == nil {\n\t\tcronJob.Spec.SuccessfulJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit\n\t}\n\tif cronJob.Spec.FailedJobsHistoryLimit == nil {\n\t\tcronJob.Spec.FailedJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit\n\t}\n}\n`\n\nconst webhooksNoticeMarker = `\n/*\nNotice that we use kubebuilder markers to generate webhook manifests.\nThis marker is responsible for generating a mutating webhook manifest.\n\nThe meaning of each marker can be found [here](/reference/markers/webhook.md).\n*/\n\n/*\nThis marker is responsible for generating a mutation webhook manifest.\n*/\n`\n\nconst explanationValidateCRD = `\n/*\nWe can validate our CRD beyond what's possible with declarative\nvalidation. Generally, declarative validation should be sufficient, but\nsometimes more advanced use cases call for complex validation.\n\nFor instance, we'll see below that we use this to validate a well-formed cron\nschedule without making up a long regular expression.\n\nIf` + \" `\" + `webhook.CustomValidator` + \"`\" + ` interface is implemented, a webhook will automatically be\nserved that calls the validation.\n\nThe` + \" `\" + `ValidateCreate` + \"`\" + `, ` + \"`\" + `ValidateUpdate` + \"`\" + ` and` + \" `\" + `ValidateDelete` + \"`\" + ` methods are expected\nto validate its receiver upon creation, update and deletion respectively.\nWe separate out ValidateCreate from ValidateUpdate to allow behavior like making\ncertain fields immutable, so that they can only be set on creation.\nValidateDelete is also separated from ValidateUpdate to allow different\nvalidation behavior on deletion.\nHere, however, we just use the same shared validation for` + \" `\" + `ValidateCreate` + \"`\" + ` and\n` + \"`\" + `ValidateUpdate` + \"`\" + `. And we do nothing in` + \" `\" + `ValidateDelete` + \"`\" + `, since we don't need to\nvalidate anything on deletion.\n*/\n\n/*\nThis marker is responsible for generating a validation webhook manifest.\n*/\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.`\n\nconst customInterfaceDefaultInfo = `/*\nWe use the ` + \"`\" + `webhook.CustomDefaulter` + \"`\" + `interface to set defaults to our CRD.\nA webhook will automatically be served that calls this defaulting.\n\nThe ` + \"`\" + `Default` + \"`\" + `method is expected to mutate the receiver, setting the defaults.\n*/\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind CronJob.`\n\nconst webhookValidateSpecMethods = `\n/*\nWe validate the name and the spec of the CronJob.\n*/\n\n// validateCronJob validates the fields of a CronJob object.\nfunc validateCronJob(cronjob *batchv1.CronJob) error {\n\tvar allErrs field.ErrorList\n\tif err := validateCronJobName(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif err := validateCronJobSpec(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif len(allErrs) == 0 {\n\t\treturn nil\n\t}\n\n\treturn apierrors.NewInvalid(\n\t\tschema.GroupKind{Group: \"batch.tutorial.kubebuilder.io\", Kind: \"CronJob\"},\n\t\tcronjob.Name, allErrs)\n}\n\n/*\nSome fields are declaratively validated by OpenAPI schema.\nYou can find kubebuilder validation markers (prefixed\nwith ` + \"`\" + `// +kubebuilder:validation` + \"`\" + `) in the\n[Designing an API](api-design.md) section.\nYou can find all of the kubebuilder supported markers for\ndeclaring validation by running ` + \"`\" + `controller-gen crd -w` + \"`\" + `,\nor [here](/reference/markers/crd-validation.md).\n*/\n\nfunc validateCronJobSpec(cronjob *batchv1.CronJob) *field.Error {\n\t// The field helpers from the kubernetes API machinery help us return nicely\n\t// structured validation errors.\n\treturn validateScheduleFormat(\n\t\tcronjob.Spec.Schedule,\n\t\tfield.NewPath(\"spec\").Child(\"schedule\"))\n}\n\n/*\nWe'll need to validate the [cron](https://en.wikipedia.org/wiki/Cron) schedule\nis well-formatted.\n*/\n\nfunc validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error {\n\tif _, err := cron.ParseStandard(schedule); err != nil {\n\t\treturn field.Invalid(fldPath, schedule, err.Error())\n\t}\n\treturn nil\n}\n\n/*\nValidating the length of a string field can be done declaratively by\nthe validation schema.\n\nBut the ` + \"`\" + `ObjectMeta.Name` + \"`\" + ` field is defined in a shared package under\nthe apimachinery repo, so we can't declaratively validate it using\nthe validation schema.\n*/\n\nfunc validateCronJobName(cronjob *batchv1.CronJob) *field.Error {\n\tif len(cronjob.Name) > validationutils.DNS1035LabelMaxLength-11 {\n\t\t// The job name length is 63 characters like all Kubernetes objects\n\t\t// (which must fit in a DNS subdomain). The cronjob controller appends\n\t\t// a 11-character suffix to the cronjob (` + \"`\" + `-$TIMESTAMP` + \"`\" + `) when creating\n\t\t// a job. The job name length limit is 63 characters. Therefore cronjob\n\t\t// names must have length <= 63-11=52. If we don't validate this here,\n\t\t// then job creation will fail later.\n\t\treturn field.Invalid(field.NewPath(\"metadata\").Child(\"name\"), cronjob.Name, \"must be no more than 52 characters\")\n\t}\n\treturn nil\n}\n\n// +kubebuilder:docs-gen:collapse=validateCronJobName() Code Implementation`\n\nconst fragmentForDefaultFields = `\n\t// Default values for various CronJob fields\n\tDefaultConcurrencyPolicy      batchv1.ConcurrencyPolicy\n\tDefaultSuspend                bool\n\tDefaultSuccessfulJobsHistoryLimit int32\n\tDefaultFailedJobsHistoryLimit int32\n`\n\nconst webhookTestCreateDefaultingFragment = `// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })`\n\nconst webhookTestCreateDefaultingReplaceFragment = `It(\"Should apply defaults when a required field is empty\", func() {\n\t\t\tBy(\"simulating a scenario where defaults should be applied\")\n\t\t\tobj.Spec.ConcurrencyPolicy = \"\"           // This should default to AllowConcurrent\n\t\t\tobj.Spec.Suspend = nil                    // This should default to false\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = nil // This should default to 3\n\t\t\tobj.Spec.FailedJobsHistoryLimit = nil     // This should default to 1\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\n\t\t\tBy(\"checking that the default values are set\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.AllowConcurrent), \"Expected ConcurrencyPolicy to default to AllowConcurrent\")\n\t\t\tExpect(*obj.Spec.Suspend).To(BeFalse(), \"Expected Suspend to default to false\")\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(3)), \"Expected SuccessfulJobsHistoryLimit to default to 3\")\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(1)), \"Expected FailedJobsHistoryLimit to default to 1\")\n\t\t})\n\n\t\tIt(\"Should not overwrite fields that are already set\", func() {\n\t\t\tBy(\"setting fields that would normally get a default\")\n\t\t\tobj.Spec.ConcurrencyPolicy = batchv1.ForbidConcurrent\n\t\t\tobj.Spec.Suspend = ptr.To(true)\n\t\t\tobj.Spec.SuccessfulJobsHistoryLimit = ptr.To(int32(5))\n\t\t\tobj.Spec.FailedJobsHistoryLimit = ptr.To(int32(2))\n\n\t\t\tBy(\"calling the Default method to apply defaults\")\n\t\t\t_ = defaulter.Default(ctx, obj)\n\t\t\t\n\t\t\tBy(\"checking that the fields were not overwritten\")\n\t\t\tExpect(obj.Spec.ConcurrencyPolicy).To(Equal(batchv1.ForbidConcurrent), \"Expected ConcurrencyPolicy to retain its set value\")\n\t\t\tExpect(obj.Spec.Suspend).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.Suspend).To(BeTrue(), \"Expected Suspend to retain its set value\")\n\t\t\tExpect(obj.Spec.SuccessfulJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.SuccessfulJobsHistoryLimit).To(Equal(int32(5)), \"Expected SuccessfulJobsHistoryLimit to retain its set value\")\n\t\t\tExpect(obj.Spec.FailedJobsHistoryLimit).NotTo(BeNil())\n\t\t\tExpect(*obj.Spec.FailedJobsHistoryLimit).To(Equal(int32(2)), \"Expected FailedJobsHistoryLimit to retain its set value\")\n})`\n\nconst webhookTestingValidatingTodoFragment = `// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })`\n\nconst webhookTestingValidatingExampleFragment = `It(\"Should deny creation if the name is too long\", func() {\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"must be no more than 52 characters\")),\n\t\t\t\t\"Expected name validation to fail for a too-long name\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the name is valid\", func() {\n\t\t\tobj.Name = validCronJobName\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected name validation to pass for a valid name\")\n\t\t})\n\n\t\tIt(\"Should deny creation if the schedule is invalid\", func() {\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).Error().To(\n\t\t\t\tMatchError(ContainSubstring(\"Expected exactly 5 fields, found 1: invalid-cron-schedule\")),\n\t\t\t\t\"Expected spec validation to fail for an invalid schedule\")\n\t\t})\n\n\t\tIt(\"Should admit creation if the schedule is valid\", func() {\n\t\t\tobj.Spec.Schedule = schedule\n\t\t\tExpect(validator.ValidateCreate(ctx, obj)).To(BeNil(),\n\t\t\t\t\"Expected spec validation to pass for a valid schedule\")\n\t\t})\n\n\t\tIt(\"Should deny update if both name and spec are invalid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"this-name-is-way-too-long-and-should-fail-validation-because-it-is-way-too-long\"\n\t\t\tobj.Spec.Schedule = \"invalid-cron-schedule\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).Error().To(HaveOccurred(),\n\t\t\t\t\"Expected validation to fail for both name and spec\")\n\t\t})\n\n\t\tIt(\"Should admit update if both name and spec are valid\", func() {\n\t\t\toldObj.Name = validCronJobName\n\t\t\toldObj.Spec.Schedule = schedule\n\n\t\t\tBy(\"simulating an update\")\n\t\t\tobj.Name = \"valid-cronjob-name-updated\"\n\t\t\tobj.Spec.Schedule = \"0 0 * * *\"\n\n\t\t\tBy(\"validating an update\")\n\t\t\tExpect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil(),\n\t\t\t\t\"Expected validation to pass for a valid update\")\n\t\t})`\n\nconst webhookTestsVars = `var (\n\t\tobj       *batchv1.CronJob\n\t\toldObj    *batchv1.CronJob\n\t\tvalidator CronJobCustomValidator\n\t\tdefaulter CronJobCustomDefaulter\n\t)`\n\nconst webhookTestsConstants = `\tvar (\n\t\tobj       *batchv1.CronJob\n\t\toldObj    *batchv1.CronJob\n\t\tvalidator CronJobCustomValidator\n\t\tdefaulter CronJobCustomDefaulter\n\t)\n\n\tconst validCronJobName = \"valid-cronjob-name\"\n\tconst schedule = \"*/5 * * * *\"`\n\nconst webhookTestsBeforeEachOriginal = `obj = &batchv1.CronJob{}\n\t\toldObj = &batchv1.CronJob{}\n\t\tvalidator = CronJobCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tdefaulter = CronJobCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")`\n\nconst webhookTestsBeforeEachChanged = `obj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*obj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*obj.Spec.FailedJobsHistoryLimit = 1\n\n\t\toldObj = &batchv1.CronJob{\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule:                   schedule,\n\t\t\t\tConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\t\tSuccessfulJobsHistoryLimit: ptr.To(int32(3)),\n\t\t\t\tFailedJobsHistoryLimit:     ptr.To(int32(1)),\n\t\t\t},\n\t\t}\n\t\t*oldObj.Spec.SuccessfulJobsHistoryLimit = 3\n\t\t*oldObj.Spec.FailedJobsHistoryLimit = 1\n\n\t\tvalidator = CronJobCustomValidator{}\n\t\tdefaulter = CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv1.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}\n\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/writing_tests_controller.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst controllerTest = `/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nIdeally, we should have one` + \" `\" + `<kind>_controller_test.go` + \"`\" + ` for each controller scaffolded and called in the` + \" `\" + `suite_test.go` + \"`\" + `.\nSo, let's write our example test for the CronJob controller (` + \"`\" + `cronjob_controller_test.go.` + \"`\" + `)\n*/\n\n/*\nAs usual, we start with the necessary imports.\n*/\npackage controller\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\tcronjobv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nThe first step to writing a simple integration test is to actually create an instance of CronJob you can run tests against.\nNote that to create a CronJob, you'll need to create a stub CronJob struct that contains your CronJob's specifications.\n\nNote that when we create a stub CronJob, the CronJob also needs stubs of its required downstream objects.\nWithout the stubbed Job template spec and the Pod template spec below, the Kubernetes API will not be able to\ncreate the CronJob.\n*/\nvar _ = Describe(\"CronJob controller\", func() {\n\tContext(\"CronJob controller test\", func() {\n\n\t\tconst CronjobName = \"test-cronjob\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &v1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      CronjobName,\n\t\t\t\tNamespace: CronjobName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      CronjobName,\n\t\t\tNamespace: CronjobName,\n\t\t}\n\t\tcronJob := &cronjobv1.CronJob{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Get(ctx, types.NamespacedName{Name: CronjobName}, &v1.Namespace{})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\terr = k8sClient.Create(ctx, namespace)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\tBy(\"creating the custom resource for the Kind CronJob\")\n\t\t\tcronJob = &cronjobv1.CronJob{}\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, cronJob)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t/*\n\t\t\t\t\tLet's mock our custom resource the same way we would apply it from\n\t\t\t\t\tthe manifest under config/samples\n\t\t\t\t*/\n\t\t\t\tcronJob = &cronjobv1.CronJob{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      CronjobName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, cronJob)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\t/*\n\t\t\tAfter each test, we clean up the resources created above.\n\t\t*/\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind CronJob\")\n\t\t\tfound := &cronjobv1.CronJob{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\t\t})\n\n\t\t/*\n\t\t\tNow we can start implementing the test that validates the controller’s reconciliation behavior.\n\t\t*/\n\n\t\tIt(\"should successfully reconcile a custom resource for CronJob\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &cronjobv1.CronJob{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tAfter creating this CronJob, let's verify that the controller properly initializes the status conditions.\n\t\t\t\tThe controller runs in the background (started in suite_test.go), so it will automatically\n\t\t\t\tdetect our CronJob and set initial conditions.\n\t\t\t*/\n\t\t\tBy(\"Checking that status conditions are initialized\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Conditions).NotTo(BeEmpty())\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tNow let's verify the CronJob has no active jobs initially.\n\t\t\t\tWe use Gomega's` + \" `\" + `Consistently()` + \"`\" + ` check here to ensure the status remains stable,\n\t\t\t\tconfirming the controller isn't creating jobs prematurely.\n\t\t\t*/\n\t\t\tBy(\"Checking that the CronJob has zero active Jobs\")\n\t\t\tConsistently(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(BeEmpty())\n\t\t\t}).WithTimeout(time.Second * 10).WithPolling(time.Millisecond * 250).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tNext, we actually create a stubbed Job that will belong to our CronJob.\n\t\t\t\tWe set the Job's status Active count to 2 to simulate the Job running two pods,\n\t\t\t\twhich means the Job is actively running.\n\n\t\t\t\tWe then set the Job's owner reference to point to our test CronJob.\n\t\t\t\tThis ensures that the test Job belongs to, and is tracked by, our test CronJob.\n\t\t\t*/\n\t\t\tBy(\"Creating a new Job owned by the CronJob\")\n\t\t\ttestJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"test-job\",\n\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// Note that your CronJob’s GroupVersionKind is required to set up this owner reference.\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\ttestJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, testJob)).To(Succeed())\n\t\t\t// Note that you can not manage the status values while creating the resource.\n\t\t\t// The status field is managed separately to reflect the current state of the resource.\n\t\t\t// Therefore, it should be updated using a PATCH or PUT operation after the resource has been created.\n\t\t\t// Additionally, it is recommended to use StatusConditions to manage the status. For further information see:\n\t\t\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status\n\t\t\ttestJob.Status.Active = 2\n\t\t\tExpect(k8sClient.Status().Update(ctx, testJob)).To(Succeed())\n\n\t\t\t/*\n\t\t\t\tAdding this Job to our test CronJob should trigger our controller's reconciler logic.\n\t\t\t\tAfter that, we can verify whether our controller eventually updates our CronJob's Status field as expected!\n\t\t\t*/\n\t\t\tBy(\"Checking that the CronJob has one active Job in status\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(HaveLen(1), \"should have exactly one active job\")\n\t\t\t\tg.Expect(cronJob.Status.Active[0].Name).To(Equal(\"test-job\"), \"the active job name should match\")\n\t\t\t}).Should(Succeed())\n\n\t\t\t/*\n\t\t\t\tFinally, let's verify that the controller properly set status conditions.\n\t\t\t\tStatus conditions are a key part of Kubernetes API conventions and allow users and other\n\t\t\t\tcontrollers to understand the resource state.\n\t\t\t\t\n\t\t\t\tWhen there are active jobs, the Available condition should be True with reason JobsActive.\n\t\t\t*/\n\t\t\tBy(\"Checking the latest Status Condition added to the CronJob instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"should have one Available condition\")\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"Available should be True\")\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"JobsActive\"), \"reason should be JobsActive\")\n\t\t})\n\t})\n})\n\n/*\n\tAfter writing all this code, you can run ` + \"`\" + `make test` + \"`\" + ` or ` + \"`\" + `go test ./...` + \"`\" + ` in your ` + \"`\" + `controllers/` + \"`\" + ` directory again to run your new test!\n*/\n`\n"
  },
  {
    "path": "hack/docs/internal/cronjob-tutorial/writing_tests_env.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cronjob\n\nconst suiteTestIntro = `\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nWhen we created the CronJob API with` + \" `\" + `kubebuilder create api` + \"`\" + ` in a [previous chapter](/cronjob-tutorial/new-api.md), Kubebuilder already did some test work for you.\nKubebuilder scaffolded a` + \" `\" + `internal/controller/suite_test.go` + \"`\" + ` file that does the bare bones of setting up a test environment.\n\nFirst, it will contain the necessary imports.\n*/\n`\n\nconst suiteTestEnv = `\n// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nNow, let's go through the code generated.\n*/\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client // You'll be using this client in your tests.\n)\n`\n\nconst suiteTestAddSchema = `\n\t/*\n\t\tThe CronJob Kind is added to the runtime scheme used by the test environment.\n\t\tThis ensures that the CronJob API is registered with the scheme, allowing the test controller to recognize and interact with CronJob resources.\n\t*/\n\terr = batchv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\t/*\n\t\tAfter the schemas, you will see the following marker.\n\t\tThis marker is what allows new schemas to be added here automatically when a new API is added to the project.\n\t*/\n\n\t// +kubebuilder:scaffold:scheme\n\n\t/*\n\t\tThe envtest environment is configured to load Custom Resource Definitions (CRDs) from the specified directory.\n\t\tThis setup enables the test environment to recognize and interact with the custom resources defined by these CRDs.\n\t*/`\n\nconst suiteTestClient = `\n\t/*\n\t\tA client is created for our test CRUD operations.\n\t*/`\n\nconst suiteTestDescription = `\n\t/*\n\t\tOne thing that this autogenerated file is missing, however, is a way to actually start your controller.\n\t\tThe code above will set up a client for interacting with your custom Kind,\n\t\tbut will not be able to test your controller behavior.\n\t\tIf you want to test your custom controller logic, you’ll need to add some familiar-looking manager logic\n\t\tto your BeforeSuite() function, so you can register your custom controller to run on this test cluster.\n\n\t\tYou may notice that the code below runs your controller with nearly identical logic to your CronJob project’s main.go!\n\t\tThe only difference is that the manager is started in a separate goroutine so it does not block the cleanup of envtest\n\t\twhen you’re done running your tests.\n\n\t\tNote that we set up both a \"live\" k8s client and a separate client from the manager. This is because when making\n\t\tassertions in tests, you generally want to assert against the live state of the API server. If you use the client\n\t\tfrom the manager (` + \"`\" + `k8sManager.GetClient` + \"`\" + `), you'd end up asserting against the contents of the cache instead, which is\n\t\tslower and can introduce flakiness into your tests. We could use the manager's ` + \"`\" + `APIReader` + \"`\" + ` to accomplish the same\n\t\tthing, but that would leave us with two clients in our test assertions and setup (one for reading, one for writing),\n\t\tand it'd be easy to make mistakes.\n\n\t\tNote that we keep the reconciler running against the manager's cache client, though -- we want our controller to\n\t\tbehave as it would in production, and we use features of the cache (like indices) in our controller which aren't\n\t\tavailable when talking directly to the API server.\n\t*/\n\tk8sManager, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t})\n\tExpect(err).ToNot(HaveOccurred())\n\n\terr = (&CronJobReconciler{\n\t\tClient: k8sManager.GetClient(),\n\t\tScheme: k8sManager.GetScheme(),\n\t}).SetupWithManager(k8sManager)\n\tExpect(err).ToNot(HaveOccurred())\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = k8sManager.Start(ctx)\n\t\tExpect(err).ToNot(HaveOccurred(), \"failed to run manager\")\n\t}()\n`\n\nconst suiteTestCleanup = `\n/*\nKubebuilder also generates boilerplate functions for cleaning up envtest and actually running your test files in your controllers/ directory.\nYou won't need to touch these.\n*/\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n/*\nNow that you have your controller running on a test cluster and a client ready to perform operations on your CronJob, we can start writing integration tests!\n*/\n`\n"
  },
  {
    "path": "hack/docs/internal/getting-started/generate_getting_started.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage gettingstarted\n\nimport (\n\t\"log/slog\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\n\thackutils \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/utils\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Sample define the sample which will be scaffolded\ntype Sample struct {\n\tctx *utils.TestContext\n}\n\n// NewSample create a new instance of the getting started sample and configure the KB CLI that will be used\nfunc NewSample(binaryPath, samplePath string) Sample {\n\tslog.Info(\"Generating the sample context of getting-started...\")\n\tctx := hackutils.NewSampleContext(binaryPath, samplePath, \"GO111MODULE=on\")\n\treturn Sample{&ctx}\n}\n\n// UpdateTutorial the getting started sample tutorial with the scaffold changes\nfunc (sp *Sample) UpdateTutorial() {\n\tsp.updateAPI()\n\tsp.updateSample()\n\tsp.updateController()\n\tsp.updateControllerTest()\n}\n\nfunc (sp *Sample) updateControllerTest() {\n\tfile := \"internal/controller/memcached_controller_test.go\"\n\terr := pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, file),\n\t\t\". \\\"github.com/onsi/gomega\\\"\",\n\t\t`. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/utils/ptr\"\n\tappsv1 \"k8s.io/api/apps/v1\"`,\n\t)\n\thackutils.CheckError(\"add imports apis\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, file),\n\t\t\"// TODO(user): Specify other spec details if needed.\",\n\t\t`Spec: cachev1alpha1.MemcachedSpec{\n\t\t\t\t\t\tSize: ptr.To(int32(1)),\n\t\t\t\t\t},`,\n\t)\n\thackutils.CheckError(\"add spec apis\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, file),\n\t\t`// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.`,\n\t\t`By(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Memcached instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(memcached.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableMemcached)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableMemcached)`,\n\t)\n\thackutils.CheckError(\"add spec apis\", err)\n}\n\nfunc (sp *Sample) updateAPI() {\n\tvar err error\n\tpath := \"api/v1alpha1/memcached_types.go\"\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`limitations under the License.\n*/`,\n\t\t`\n// +kubebuilder:docs-gen:collapse=Apache License\n\n`)\n\thackutils.CheckError(\"collapse license in memcached api\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`Any new fields you add must have json tags for the fields to be serialized.\n`,\n\t\t`\n// +kubebuilder:docs-gen:collapse=Imports\n`)\n\thackutils.CheckError(\"collapse imports in memcached api\", err)\n\n\terr = pluginutil.ReplaceInFile(filepath.Join(sp.ctx.Dir, path), oldSpecAPI, newSpecAPI)\n\thackutils.CheckError(\"replace spec api\", err)\n}\n\nfunc (sp *Sample) updateSample() {\n\tfile := filepath.Join(sp.ctx.Dir, \"config/samples/cache_v1alpha1_memcached.yaml\")\n\terr := pluginutil.ReplaceInFile(file, \"# TODO(user): Add fields here\", sampleSizeFragment)\n\thackutils.CheckError(\"update sample to add size\", err)\n}\n\nfunc (sp *Sample) updateController() {\n\tpathFile := \"internal/controller/memcached_controller.go\"\n\terr := pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"\\\"context\\\"\",\n\t\tcontrollerImports,\n\t)\n\thackutils.CheckError(\"add imports\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"cachev1alpha1 \\\"example.com/memcached/api/v1alpha1\\\"\\n)\",\n\t\tcontrollerStatusTypes,\n\t)\n\thackutils.CheckError(\"add status types\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\tcontrollerInfoReconcileOld,\n\t\tcontrollerInfoReconcileNew,\n\t)\n\thackutils.CheckError(\"add status types\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update\",\n\t\t`\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch`,\n\t)\n\thackutils.CheckError(\"add markers\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"_ = logf.FromContext(ctx)\",\n\t\t\"log := logf.FromContext(ctx)\",\n\t)\n\thackutils.CheckError(\"add log var\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"// TODO(user): your logic here\",\n\t\tcontrollerReconcileImplementation,\n\t)\n\thackutils.CheckError(\"add reconcile implementation\", err)\n\n\terr = pluginutil.AppendCodeIfNotExist(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\tcontrollerDeploymentFunc,\n\t)\n\thackutils.CheckError(\"add func to create Deployment\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, pathFile),\n\t\t\"For(&cachev1alpha1.Memcached{}).\",\n\t\t`\n\t\tOwns(&appsv1.Deployment{}).`,\n\t)\n\thackutils.CheckError(\"add reconcile implementation\", err)\n}\n\n// Prepare the Context for the sample project\nfunc (sp *Sample) Prepare() {\n\tslog.Info(\"Destroying directory for getting-started sample project\")\n\tsp.ctx.Destroy()\n\n\tslog.Info(\"Refreshing tools and creating directory...\")\n\terr := sp.ctx.Prepare()\n\n\thackutils.CheckError(\"Creating directory for sample project\", err)\n}\n\n// GenerateSampleProject will generate the sample\nfunc (sp *Sample) GenerateSampleProject() {\n\tslog.Info(\"Initializing the getting started project\")\n\terr := sp.ctx.Init(\n\t\t\"--domain\", \"example.com\",\n\t\t\"--repo\", \"example.com/memcached\",\n\t\t\"--license\", \"apache2\",\n\t\t\"--owner\", \"The Kubernetes authors\",\n\t)\n\thackutils.CheckError(\"Initializing the getting started project\", err)\n\n\tslog.Info(\"Adding a new config type\")\n\terr = sp.ctx.CreateAPI(\n\t\t\"--group\", \"cache\",\n\t\t\"--version\", \"v1alpha1\",\n\t\t\"--kind\", \"Memcached\",\n\t\t\"--resource\", \"--controller\",\n\t)\n\thackutils.CheckError(\"Creating the API\", err)\n\n\tslog.Info(\"Adding AutoUpdate Plugin\")\n\terr = sp.ctx.Edit(\n\t\t\"--plugins\", \"autoupdate/v1-alpha\",\n\t)\n\thackutils.CheckError(\"Initializing the getting started project\", err)\n}\n\n// CodeGen will call targets to generate code\nfunc (sp *Sample) CodeGen() {\n\tcmd := exec.Command(\"go\", \"mod\", \"tidy\")\n\t_, err := sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run go mod tidy all for getting started tutorial\", err)\n\n\tcmd = exec.Command(\"make\", \"all\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make all for getting started tutorial\", err)\n\n\tcmd = exec.Command(\"make\", \"build-installer\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make build-installer for getting started tutorial\", err)\n\n\terr = sp.ctx.EditHelmPlugin()\n\thackutils.CheckError(\"Failed to enable helm plugin\", err)\n}\n\nconst (\n\toldSpecAPI = \"// foo is an example field of Memcached. Edit memcached_types.go to remove/update\\n\\t// +optional\\n\\tFoo *string `json:\\\"foo,omitempty\\\"`\"\n\tnewSpecAPI = `// size defines the number of Memcached instances\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\t// +kubebuilder:validation:Minimum=1\n\t// +kubebuilder:validation:Maximum=3\n\t// +kubebuilder:validation:ExclusiveMaximum=false\n\t// +optional\n\tSize *int32 ` + \"`json:\\\"size,omitempty\\\"`\"\n)\n\nconst sampleSizeFragment = `# TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1`\n\nconst controllerImports = `\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n`\n\nconst controllerStatusTypes = `\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableMemcached represents the status of the Deployment reconciliation\n\ttypeAvailableMemcached = \"Available\"\n)`\n\nconst controllerInfoReconcileOld = `// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Memcached object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.`\n\nconst controllerInfoReconcileNew = `// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/`\n\nconst controllerReconcileImplementation = `// Fetch the Memcached instance\n\t// The purpose is check if the Custom Resource for the Kind Memcached\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tmemcached := &cachev1alpha1.Memcached{}\n\terr := r.Get(ctx, req.NamespacedName, memcached)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Memcached resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get memcached\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Let's just set the status as Unknown when no status is available\n\tif len(memcached.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the memcached Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForMemcached(memcached)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Memcached\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-trigged again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif memcached.Spec.Size != nil {\n\t\tdesiredReplicas = *memcached.Spec.Size\n\t}\n\n\t// The CRD API defines that the Memcached type have a MemcachedSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", memcached.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\treturn ctrl.Result{}, err\n\t}`\n\nconst controllerDeploymentFunc = `// deploymentForMemcached returns a Memcached Deployment object\nfunc (r *MemcachedReconciler) deploymentForMemcached(\n\tmemcached *cachev1alpha1.Memcached) (*appsv1.Deployment, error) {\n\timage := \"memcached:1.6.26-alpine3.19\"\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      memcached.Name,\n\t\t\tNamespace: memcached.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: memcached.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\"app.kubernetes.io/name\": \"project\"},\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: map[string]string{\"app.kubernetes.io/name\": \"project\"},\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"memcached\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tRunAsUser:                ptr.To(int64(1001)),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"--memory-limit=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/controller_tests_code.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst multiversionControllerTest = `/*\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\n\tcronjobv1 \"tutorial.kubebuilder.io/project/api/v1\"\n)\n\nvar _ = Describe(\"CronJob controller\", func() {\n\tContext(\"CronJob controller test\", func() {\n\n\t\tconst NamespaceName = \"test-cronjob\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &v1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      NamespaceName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t},\n\t\t}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Get(ctx, types.NamespacedName{Name: NamespaceName}, &v1.Namespace{})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\terr = k8sClient.Create(ctx, namespace)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// Note: We don't delete the namespace here to avoid issues with parallel test execution.\n\t\t\t// The namespace will be cleaned up when the test suite finishes.\n\t\t})\n\n\t\tIt(\"should initialize status conditions on first reconciliation\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that status conditions are initialized\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Conditions).NotTo(BeEmpty())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set AllJobsCompleted condition when no active jobs exist\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that the CronJob has zero active Jobs\")\n\t\t\tConsistently(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(BeEmpty())\n\t\t\t}).WithTimeout(time.Second * 5).WithPolling(time.Millisecond * 250).Should(Succeed())\n\n\t\t\tBy(\"Checking AllJobsCompleted condition\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tif len(availableConditions) > 0 {\n\t\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"AllJobsCompleted\"))\n\t\t\t}\n\n\t\t\tvar progressingConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Progressing\")), &progressingConditions))\n\t\t\tif len(progressingConditions) > 0 {\n\t\t\t\tExpect(progressingConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\tExpect(progressingConditions[0].Reason).To(Equal(\"NoJobsActive\"))\n\t\t\t}\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should track active jobs and set JobsActive condition\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Creating an active Job owned by the CronJob\")\n\t\t\ttestJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"test-job-%d\", GinkgoRandomSeed()),\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\ttestJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, testJob)).To(Succeed())\n\n\t\t\ttestJob.Status.Active = 2\n\t\t\tExpect(k8sClient.Status().Update(ctx, testJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that the CronJob has one active Job in status\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tg.Expect(cronJob.Status.Active).To(HaveLen(1))\n\t\t\t\tg.Expect(cronJob.Status.Active[0].Name).To(Equal(testJob.Name))\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking JobsActive conditions\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tExpect(availableConditions).To(HaveLen(1))\n\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"JobsActive\"))\n\n\t\t\tvar progressingConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Progressing\")), &progressingConditions))\n\t\t\tExpect(progressingConditions).To(HaveLen(1))\n\t\t\tExpect(progressingConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\tExpect(progressingConditions[0].Reason).To(Equal(\"JobsActive\"))\n\n\t\t\tBy(\"Cleaning up\")\n\t\t\tExpect(k8sClient.Delete(ctx, testJob)).To(Succeed())\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set Degraded condition when jobs fail\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Creating a failed Job owned by the CronJob\")\n\t\t\tfailedJob := &batchv1.Job{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      fmt.Sprintf(\"test-job-failed-%d\", GinkgoRandomSeed()),\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tkind := reflect.TypeFor[cronjobv1.CronJob]().Name()\n\t\t\tgvk := cronjobv1.GroupVersion.WithKind(kind)\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tcontrollerRef := metav1.NewControllerRef(cronJob, gvk)\n\t\t\tfailedJob.SetOwnerReferences([]metav1.OwnerReference{*controllerRef})\n\t\t\tExpect(k8sClient.Create(ctx, failedJob)).To(Succeed())\n\n\t\t\tnow := metav1.Now()\n\t\t\tfailedJob.Status.StartTime = &now\n\t\t\tfailedJob.Status.Conditions = append(failedJob.Status.Conditions,\n\t\t\t\tbatchv1.JobCondition{\n\t\t\t\t\tType:   batchv1.JobFailureTarget,\n\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t},\n\t\t\t\tbatchv1.JobCondition{\n\t\t\t\t\tType:   batchv1.JobFailed,\n\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t})\n\t\t\tExpect(k8sClient.Status().Update(ctx, failedJob)).To(Succeed())\n\n\t\t\tBy(\"Checking that Degraded=True when jobs fail\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tvar degradedConditions []metav1.Condition\n\t\t\t\tg.Expect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\t\tHaveField(\"Type\", Equal(\"Degraded\")), &degradedConditions))\n\t\t\t\tif len(degradedConditions) > 0 {\n\t\t\t\t\tg.Expect(degradedConditions[0].Status).To(Equal(metav1.ConditionTrue))\n\t\t\t\t\tg.Expect(degradedConditions[0].Reason).To(Equal(\"JobsFailed\"))\n\t\t\t\t}\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking that Available=False when jobs fail\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\tvar availableConditions []metav1.Condition\n\t\t\tExpect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\tif len(availableConditions) > 0 {\n\t\t\t\tExpect(availableConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\tExpect(availableConditions[0].Reason).To(Equal(\"JobsFailed\"))\n\t\t\t}\n\n\t\t\tBy(\"Cleaning up\")\n\t\t\tExpect(k8sClient.Delete(ctx, failedJob)).To(Succeed())\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should set Available=False when CronJob is suspended\", func() {\n\t\t\tcronJobName := fmt.Sprintf(\"test-cronjob-%d\", GinkgoRandomSeed())\n\t\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\t\tName:      cronJobName,\n\t\t\t\tNamespace: NamespaceName,\n\t\t\t}\n\n\t\t\tcronJob := &cronjobv1.CronJob{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      cronJobName,\n\t\t\t\t\tNamespace: NamespaceName,\n\t\t\t\t},\n\t\t\t\tSpec: cronjobv1.CronJobSpec{\n\t\t\t\t\tSchedule: \"1 * * * *\",\n\t\t\t\t\tJobTemplate: batchv1.JobTemplateSpec{\n\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tName:  \"test-container\",\n\t\t\t\t\t\t\t\t\t\t\tImage: \"test-image\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tRestartPolicy: v1.RestartPolicyOnFailure,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, cronJob)).To(Succeed())\n\n\t\t\tBy(\"Updating the CronJob to suspend it\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tcronJob.Spec.Suspend = ptr.To(true)\n\t\t\t\tg.Expect(k8sClient.Update(ctx, cronJob)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Checking that Available=False when suspended\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, cronJob)).To(Succeed())\n\t\t\t\tvar availableConditions []metav1.Condition\n\t\t\t\tg.Expect(cronJob.Status.Conditions).To(ContainElement(\n\t\t\t\t\tHaveField(\"Type\", Equal(\"Available\")), &availableConditions))\n\t\t\t\tif len(availableConditions) > 0 {\n\t\t\t\t\tg.Expect(availableConditions[0].Status).To(Equal(metav1.ConditionFalse))\n\t\t\t\t\tg.Expect(availableConditions[0].Reason).To(Equal(\"Suspended\"))\n\t\t\t\t}\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Cleaning up the CronJob\")\n\t\t\tExpect(k8sClient.Delete(ctx, cronJob)).To(Succeed())\n\t\t})\n\t})\n})\n`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/cronjob_v1.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst cronjobSpecComment = `/*\n First, let's take a look at our spec.  As we discussed before, spec holds\n *desired state*, so any \"inputs\" to our controller go here.\n\n Fundamentally a CronJob needs the following pieces:\n\n - A schedule (the *cron* in CronJob)\n - A template for the Job to run (the\n *job* in CronJob)\n\n We'll also want a few extras, which will make our users' lives easier:\n\n - A deadline for starting jobs (if we miss this deadline, we'll just wait till\n   the next scheduled time)\n - What to do if multiple jobs would run at once (do we wait? stop the old one? run both?)\n - A way to pause the running of a CronJob, in case something's wrong with it\n - Limits on old job history\n\n Remember, since we never read our own status, we need to have some other way to\n keep track of whether a job has run.  We can use at least one old job to do\n this.\n\n We'll use several markers (` + \"`// +comment`\" + `) to specify additional metadata.  These\n will be used by [controller-tools](https://github.com/kubernetes-sigs/controller-tools) when generating our CRD manifest.\n As we'll see in a bit, controller-tools will also use GoDoc to form descriptions for\n the fields.\n*/`\n\nconst concurrencyPolicyComment = `/*\n We define a custom type to hold our concurrency policy.  It's actually\n just a string under the hood, but the type gives extra documentation,\n and allows us to attach validation on the type instead of the field,\n making the validation more easily reusable.\n*/`\n\nconst statusDesignComment = `/*\n Next, let's design our status, which holds observed state.  It contains any information\n we want users or other controllers to be able to easily obtain.\n\n We'll keep a list of actively running jobs, as well as the last time that we successfully\n ran our job.  Notice that we use ` + \"`metav1.Time`\" + ` instead of ` + \"`time.Time`\" + ` to get the stable\n serialization, as mentioned above.\n*/`\n\nconst boilerplateReplacement = `// +kubebuilder:docs-gen:collapse=Remaining code from cronjob_types.go\n\n/*\n Since we'll have more than one version, we'll need to mark a storage version.\n This is the version that the Kubernetes API server uses to store our data.\n We'll chose the v1 version for our project.\n\n We'll use the [` + \"`+kubebuilder:storageversion`\" + `](/reference/markers/crd.md) to do this.\n\n Note that multiple versions may exist in storage if they were written before\n the storage version changes -- changing the storage version only affects how\n objects are created/updated after the change.\n*/\n\n// +kubebuilder:object:root=true\n// +kubebuilder:storageversion`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/cronjob_v2.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst importV2 = `import (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)`\n\nconst importReplacement = `/*\n */\nimport (\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)`\n\n// FIXME: We should just insert and replace what is need and not a block of code in this way\nconst cronjobSpecMore = `// startingDeadlineSeconds defines in seconds for starting the job if it misses scheduled\n\t// time for any reason.  Missed jobs executions will be counted as failed ones.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tStartingDeadlineSeconds *int64 ` + \"`json:\\\"startingDeadlineSeconds,omitempty\\\"`\" + `\n\n\t// concurrencyPolicy defines how to treat concurrent executions of a Job.\n\t// Valid values are:\n\t// - \"Allow\" (default): allows CronJobs to run concurrently;\n\t// - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet;\n\t// - \"Replace\": cancels currently running job and replaces it with a new one\n\t// +optional\n\t// +kubebuilder:default:=Allow\n\tConcurrencyPolicy ConcurrencyPolicy ` + \"`json:\\\"concurrencyPolicy,omitempty\\\"`\" + `\n\n\t// suspend tells the controller to suspend subsequent executions, it does\n\t// not apply to already started executions.  Defaults to false.\n\t// +optional\n\tSuspend *bool ` + \"`json:\\\"suspend,omitempty\\\"`\" + `\n\n\t// jobTemplate defines the job that will be created when executing a CronJob.\n\t// +required\n\tJobTemplate batchv1.JobTemplateSpec ` + \"`json:\\\"jobTemplate\\\"`\" + `\n\n\t// successfulJobsHistoryLimit defines the number of successful finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tSuccessfulJobsHistoryLimit *int32 ` + \"`json:\\\"successfulJobsHistoryLimit,omitempty\\\"`\" + `\n\n\t// failedJobsHistoryLimit defines the number of failed finished jobs to retain.\n\t// This is a pointer to distinguish between explicit zero and not specified.\n\t// +optional\n\t// +kubebuilder:validation:Minimum=0\n\tFailedJobsHistoryLimit *int32 ` + \"`json:\\\"failedJobsHistoryLimit,omitempty\\\"`\" + `\n}\n\n// +kubebuilder:docs-gen:collapse=CronJobSpec Full Code\n\n/*\nNext, we'll need to define a type to hold our schedule.\nBased on our proposed YAML above, it'll have a field for\neach corresponding Cron \"field\".\n*/\n\n// describes a Cron schedule.\ntype CronSchedule struct {\n\t// minute specifies the minutes during which the job executes.\n\t// +optional\n\tMinute *CronField ` + \"`json:\\\"minute,omitempty\\\"`\" + `\n\t// hour specifies the hour during which the job executes.\n\t// +optional\n\tHour *CronField ` + \"`json:\\\"hour,omitempty\\\"`\" + `\n\t// dayOfMonth specifies the day of the month during which the job executes.\n\t// +optional\n\tDayOfMonth *CronField ` + \"`json:\\\"dayOfMonth,omitempty\\\"`\" + `\n\t// month specifies the month during which the job executes.\n\t// +optional\n\tMonth *CronField ` + \"`json:\\\"month,omitempty\\\"`\" + `\n\t// dayOfWeek specifies the day of the week during which the job executes.\n\t// +optional\n\tDayOfWeek *CronField ` + \"`json:\\\"dayOfWeek,omitempty\\\"`\" + `\n}\n\n/*\nFinally, we'll define a wrapper type to represent a field.\nWe could attach additional validation to this field,\nbut for now we'll just use it for documentation purposes.\n*/\n\n// represents a Cron field specifier.\ntype CronField string\n\n/*\nAll the other types will stay the same as before.\n*/\n\n// ConcurrencyPolicy describes how the job will be handled.\n// Only one of the following concurrent policies may be specified.\n// If none of the following policies is specified, the default one\n// is AllowConcurrent.\n// +kubebuilder:validation:Enum=Allow;Forbid;Replace\ntype ConcurrencyPolicy string\n\nconst (\n\t// AllowConcurrent allows CronJobs to run concurrently.\n\tAllowConcurrent ConcurrencyPolicy = \"Allow\"\n\n\t// ForbidConcurrent forbids concurrent runs, skipping next run if previous\n\t// hasn't finished yet.\n\tForbidConcurrent ConcurrencyPolicy = \"Forbid\"\n\n\t// ReplaceConcurrent cancels currently running job and replaces it with a new one.\n\tReplaceConcurrent ConcurrencyPolicy = \"Replace\"\n)\n`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/generate_multiversion.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nimport (\n\tlog \"log/slog\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/afero\"\n\n\thackutils \"sigs.k8s.io/kubebuilder/v4/hack/docs/internal/utils\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Sample define the sample which will be scaffolded\ntype Sample struct {\n\tctx *utils.TestContext\n}\n\n// NewSample create a new instance of the sample and configure the KB CLI that will be used\nfunc NewSample(binaryPath, samplePath string) Sample {\n\tlog.Info(\"Generating the sample context of MultiVersion Cronjob...\")\n\tctx := hackutils.NewSampleContext(binaryPath, samplePath, \"GO111MODULE=on\")\n\treturn Sample{&ctx}\n}\n\n// Prepare the Context for the sample project\nfunc (sp *Sample) Prepare() {\n\tlog.Info(\"refreshing tools and creating directory for multiversion ...\")\n\terr := sp.ctx.Prepare()\n\thackutils.CheckError(\"creating directory for multiversion project\", err)\n}\n\n// GenerateSampleProject will generate the sample\nfunc (sp *Sample) GenerateSampleProject() {\n\tlog.Info(\"Initializing the multiversion cronjob project\")\n\n\tlog.Info(\"Creating v2 API\")\n\terr := sp.ctx.CreateAPI(\n\t\t\"--group\", \"batch\",\n\t\t\"--version\", \"v2\",\n\t\t\"--kind\", \"CronJob\",\n\t\t\"--resource=true\",\n\t\t\"--controller=false\",\n\t)\n\thackutils.CheckError(\"Creating the v2 API without controller\", err)\n\n\tlog.Info(\"Creating conversion webhook for v1\")\n\terr = sp.ctx.CreateWebhook(\n\t\t\"--group\", \"batch\",\n\t\t\"--version\", \"v1\",\n\t\t\"--kind\", \"CronJob\",\n\t\t\"--conversion\",\n\t\t\"--spoke\", \"v2\",\n\t)\n\thackutils.CheckError(\"Creating conversion webhook for v1\", err)\n\n\tlog.Info(\"Creating defaulting and validation webhook for v2\")\n\terr = sp.ctx.CreateWebhook(\n\t\t\"--group\", \"batch\",\n\t\t\"--version\", \"v2\",\n\t\t\"--kind\", \"CronJob\",\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t)\n\thackutils.CheckError(\"Creating defaulting and validation webhook for v2\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t`// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CronJobCustomValidator struct {`,\n\t\t`// +kubebuilder:docs-gen:collapse=Remaining Webhook Code`)\n\thackutils.CheckError(\"adding marker collapse\", err)\n}\n\n// UpdateTutorial the muilt-version sample tutorial with the scaffold changes\nfunc (sp *Sample) UpdateTutorial() {\n\tlog.Info(\"Update tutorial with multiversion code\")\n\n\t// Update files according to the multiversion\n\tsp.updateCronjobV1ForConversion()\n\tsp.updateAPIV1()\n\tsp.updateAPIV2()\n\tsp.updateWebhookV2()\n\n\tpath := \"internal/webhook/v1/cronjob_webhook_test.go\"\n\terr := pluginutil.InsertCode(filepath.Join(sp.ctx.Dir, path),\n\t\t`// TODO (user): Add any additional imports if needed`,\n\t\t`\n\t\"k8s.io/utils/ptr\"`)\n\thackutils.CheckError(\"add import for webhook tests\", err)\n\n\tsp.updateConversionFiles()\n\tsp.updateSampleV2()\n\tsp.updateMain()\n\tsp.updateControllerTest()\n\tsp.updateE2EWebhookConversion()\n}\n\nfunc (sp *Sample) updateControllerTest() {\n\ttestContent := []byte(multiversionControllerTest)\n\tfs := afero.NewOsFs()\n\ttestPath := filepath.Join(sp.ctx.Dir, \"internal/controller/cronjob_controller_test.go\")\n\terr := afero.WriteFile(fs, testPath, testContent, 0o600)\n\thackutils.CheckError(\"replacing controller test for multiversion\", err)\n}\n\nfunc (sp *Sample) updateCronjobV1ForConversion() {\n\tpath := \"internal/webhook/v1/cronjob_webhook.go\"\n\terr := pluginutil.ReplaceInFile(filepath.Join(sp.ctx.Dir, path),\n\t\t\"Then, we set up the webhook with the manager.\",\n\t\t`This setup doubles as setup for our conversion webhooks: as long as our\ntypes implement the\n[Hub](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub) and\n[Convertible](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible)\ninterfaces, a conversion webhook will be registered.\n`)\n\thackutils.CheckError(\"manager fix doc comment\", err)\n\n\terr = pluginutil.ReplaceInFile(filepath.Join(sp.ctx.Dir, \"internal/webhook/v1/cronjob_webhook.go\"),\n\t\t\"// +kubebuilder:docs-gen:collapse=validateCronJobName() Code Implementation\",\n\t\t``)\n\thackutils.CheckError(\"removing collapse valida for cronjob tutorial\", err)\n}\n\nfunc (sp *Sample) updateSampleV2() {\n\tpath := filepath.Join(sp.ctx.Dir, \"config/samples/batch_v2_cronjob.yaml\")\n\toldText := `# TODO(user): Add fields here`\n\n\terr := pluginutil.ReplaceInFile(\n\t\tpath,\n\t\toldText,\n\t\tsampleV2Code,\n\t)\n\thackutils.CheckError(\"replacing TODO with sampleV2Code in batch_v2_cronjob.yaml\", err)\n}\n\nfunc (sp *Sample) updateConversionFiles() {\n\tpath := filepath.Join(sp.ctx.Dir, \"api/v1/cronjob_conversion.go\")\n\n\terr := pluginutil.InsertCodeIfNotExist(path,\n\t\t\"limitations under the License.\\n*/\",\n\t\t\"\\n// +kubebuilder:docs-gen:collapse=Apache License\")\n\thackutils.CheckError(\"appending into hub v1 collapse docs\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t\"// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\",\n\t\thubV1CodeComment)\n\thackutils.CheckError(\"adding comment to hub v1\", err)\n\n\tpath = filepath.Join(sp.ctx.Dir, \"api/v2/cronjob_conversion.go\")\n\n\terr = pluginutil.InsertCodeIfNotExist(path,\n\t\t\"limitations under the License.\\n*/\",\n\t\t\"\\n// +kubebuilder:docs-gen:collapse=Apache License\")\n\thackutils.CheckError(\"appending into hub v2 collapse docs\", err)\n\n\terr = pluginutil.InsertCode(path,\n\t\t\"import (\",\n\t\t`\n\t\"fmt\"\n\t\"strings\"\n\n`)\n\thackutils.CheckError(\"adding imports to hub v2\", err)\n\n\terr = pluginutil.InsertCodeIfNotExist(path,\n\t\t\"batchv1 \\\"tutorial.kubebuilder.io/project/api/v1\\\"\\n)\",\n\t\t`// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nOur \"spoke\" versions need to implement the\n[`+\"`\"+`Convertible`+\"`\"+`](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Convertible)\ninterface. Namely, they'll need `+\"`\"+`ConvertTo()`+\"`\"+` and `+\"`\"+`ConvertFrom()`+\"`\"+`\nmethods to convert to/from the hub version.\n*/\n`)\n\thackutils.CheckError(\"appending into hub v2 collapse docs\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t\"package v2\",\n\t\thubV2CodeComment)\n\thackutils.CheckError(\"adding comment to hub v2\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t`// TODO(user): Implement conversion logic from v2 to v1\n\t// Example: Copying Spec fields\n\t// dst.Spec.Size = src.Spec.Replicas\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}`,\n\t\thubV2CovertTo)\n\thackutils.CheckError(\"replace covertTo at hub v2\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t`// TODO(user): Implement conversion logic from v1 to v2\n\t// Example: Copying Spec fields\n\t// dst.Spec.Replicas = src.Spec.Size\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n`,\n\t\thubV2ConvertFromCode)\n\thackutils.CheckError(\"replace covert from at hub v2\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t\"// ConvertFrom converts the Hub version (v1) to this CronJob (v2).\",\n\t\t`/*\nConvertFrom is expected to modify its receiver to contain the converted object.\nMost of the conversion is straightforward copying, except for converting our changed field.\n*/\n\n// ConvertFrom converts the Hub version (v1) to this CronJob (v2).`)\n\thackutils.CheckError(\"replace covert from info at hub v2\", err)\n\n\terr = pluginutil.ReplaceInFile(path,\n\t\t\"// ConvertTo converts this CronJob (v2) to the Hub version (v1).\",\n\t\t`/*\nConvertTo is expected to modify its argument to contain the converted object.\nMost of the conversion is straightforward copying, except for converting our changed field.\n*/\n\n// ConvertTo converts this CronJob (v2) to the Hub version (v1).`)\n\thackutils.CheckError(\"replace covert info at hub v2\", err)\n}\n\nfunc (sp *Sample) updateAPIV1() {\n\tpath := \"api/v1/cronjob_types.go\"\n\terr := pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:subresource:status\n`,\n\t\t`// +versionName=v1\n// +kubebuilder:storageversion`,\n\t)\n\thackutils.CheckError(\"add version and marker for storage version\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\tcronjobSpecComment,\n\t\t\"\",\n\t)\n\thackutils.CheckError(\"removing spec explanation\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`type CronJob struct {\n\t/*\n\t */`,\n\t\t`type CronJob struct {`,\n\t)\n\thackutils.CheckError(\"removing comment empty from struct\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`/*\n Finally, we have the rest of the boilerplate that we've already discussed.\n As previously noted, we don't need to change this, except to mark that\n we want a status subresource, so that we behave like built-in kubernetes types.\n*/`,\n\t\t``,\n\t)\n\thackutils.CheckError(\"removing comment from cronjob tutorial\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob`,\n\t\t`/*\n */\n\n// +kubebuilder:object:root=true\n\n// CronJobList contains a list of CronJob`,\n\t)\n\thackutils.CheckError(\"add comment empty after struct\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\tconcurrencyPolicyComment,\n\t\t\"\",\n\t)\n\thackutils.CheckError(\"removing concurrency policy explanation\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\tstatusDesignComment,\n\t\t\"\",\n\t)\n\thackutils.CheckError(\"removing status design explanation\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:object:root=true\n// +kubebuilder:storageversion`,\n\t\tboilerplateReplacement,\n\t)\n\thackutils.CheckError(\"add comment with storage version explanation\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, \"api/v1/cronjob_types.go\"),\n\t\t`// +kubebuilder:docs-gen:collapse=Root Object Definitions`,\n\t\t`// +kubebuilder:docs-gen:collapse=Remaining code from cronjob_types.go`,\n\t)\n\thackutils.CheckError(\"replacing docs-gen collapse comment\", err)\n}\n\nfunc (sp *Sample) updateWebhookV2() {\n\tpath := \"internal/webhook/v2/cronjob_webhook.go\"\n\n\terr := pluginutil.InsertCodeIfNotExist(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t\"limitations under the License.\\n*/\",\n\t\t\"\\n// +kubebuilder:docs-gen:collapse=Apache License\")\n\thackutils.CheckError(\"adding Apache License collapse marker to webhook v2\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`import (\n\t\"context\"`,\n\t\t`\n\t\"strings\"\n\n\t\"github.com/robfig/cron\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tvalidationutils \"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/apimachinery/pkg/util/validation/field\"`,\n\t)\n\thackutils.CheckError(\"replacing imports in v2\", err)\n\n\t// Add collapse marker after the import block to hide imports\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`batchv2 \"tutorial.kubebuilder.io/project/api/v2\"\n)`,\n\t\t`\n\n// +kubebuilder:docs-gen:collapse=Imports`)\n\thackutils.CheckError(\"adding imports collapse marker to webhook v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// TODO(user): Add more fields as needed for defaulting`,\n\t\tcronJobFieldsForDefaulting,\n\t)\n\thackutils.CheckError(\"replacing defaulting fields in v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// TODO(user): fill in your defaulting logic.\n\n\treturn nil`,\n\t\tcronJobDefaultingLogic,\n\t)\n\thackutils.CheckError(\"replacing defaulting logic in v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil`,\n\t\t`return nil, validateCronJob(obj)`,\n\t)\n\thackutils.CheckError(\"replacing validation logic for creation in v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil`,\n\t\t`return nil, validateCronJob(newObj)`,\n\t)\n\thackutils.CheckError(\"replacing validation logic for update in v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\toriginalSetupManager,\n\t\treplaceSetupManager,\n\t)\n\thackutils.CheckError(\"replacing SetupWebhookWithManager in v2\", err)\n\n\terr = pluginutil.AppendCodeAtTheEnd(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\tcronJobDefaultFunction,\n\t)\n\thackutils.CheckError(\"adding Default function in v2\", err)\n\n\t// Add the validateCronJob function\n\terr = pluginutil.AppendCodeAtTheEnd(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\tcronJobValidationFunction,\n\t)\n\thackutils.CheckError(\"adding validateCronJob function in v2\", err)\n}\n\nfunc (sp *Sample) updateMain() {\n\tpath := \"cmd/main.go\"\n\n\terr := pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`\"k8s.io/apimachinery/pkg/runtime\"`,\n\t\t`kbatchv1 \"k8s.io/api/batch/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"`,\n\t)\n\thackutils.CheckError(\"add import main.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`utilruntime.Must(batchv1.AddToScheme(scheme))`,\n\t\t`utilruntime.Must(kbatchv1.AddToScheme(scheme)) // we've added this ourselves\n\tutilruntime.Must(batchv1.AddToScheme(scheme))`,\n\t)\n\thackutils.CheckError(\"add schema main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:scaffold:scheme\n}`,\n\t\t`\n\n// +kubebuilder:docs-gen:collapse=existing setup\n\n/*\n */`,\n\t)\n\thackutils.CheckError(\"insert doc marker existing setup main.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`if err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\t// +kubebuilder:docs-gen:collapse=Remaining code from main.go`,\n\t\t`if err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}`,\n\t)\n\thackutils.CheckError(\"remove doc marker old staff from main.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`/*\nThe first difference to notice is that kubebuilder has added the new API\ngroup's package (`+\"`batchv1`\"+`) to our scheme.  This means that we can use those\nobjects in our controller.\n\nIf we would be using any other CRD we would have to add their scheme the same way.\nBuiltin types such as Job have their scheme added by `+\"`clientgoscheme`\"+`.\n*/`,\n\t\t\"\",\n\t)\n\thackutils.CheckError(\"remove API group explanation main.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`/*\nThe other thing that's changed is that kubebuilder has added a block calling our\nCronJob controller's `+\"`SetupWithManager`\"+` method.\n*/`,\n\t\t\"\",\n\t)\n\thackutils.CheckError(\"remove SetupWithManager explanation main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:docs-gen:collapse=Imports`,\n\t\t`\n\n/*\n */\n`,\n\t)\n\thackutils.CheckError(\"insert comment after import in the main.go\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`if !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}`,\n\t\t`\n\n\t// +kubebuilder:docs-gen:collapse=Manager Setup`,\n\t)\n\thackutils.CheckError(\"adding manager setup collapse marker main.go\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`/*\n\t\tWe'll also set up webhooks for our type, which we'll talk about next.\n\t\tWe just need to add them to the manager.  Since we might want to run\n\t\tthe webhooks separately, or not run them when testing our controller\n\t\tlocally, we'll put them behind an environment variable.\n\n\t\tWe'll just make sure to set `+\"`ENABLE_WEBHOOKS=false`\"+` when we run locally.\n\t*/`,\n\t\t`/*\n\t\tOur existing call to SetupWebhookWithManager registers our conversion webhooks with the manager, too.\n\t*/`,\n\t)\n\thackutils.CheckError(\"replace webhook setup explanation main.go\", err)\n}\n\nfunc (sp *Sample) updateAPIV2() {\n\tpath := \"api/v2/cronjob_types.go\"\n\terr := pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// +kubebuilder:subresource:status\n`,\n\t\t`// +versionName=v2`,\n\t)\n\thackutils.CheckError(\"add marker version for v2\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t\"limitations under the License.\\n*/\", // This is the anchor point where we want to insert the code\n\t\t`\n// +kubebuilder:docs-gen:collapse=Apache License\n\n/*\nSince we're in a v2 package, controller-gen will assume this is for the v2\nversion automatically.  We could override that with the [`+\"`+versionName`\"+`\nmarker](/reference/markers/crd.md).\n*/`)\n\thackutils.CheckError(\"insert doc marker for license\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\timportV2,\n\t\timportReplacement,\n\t)\n\thackutils.CheckError(\"replace imports v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// CronJobSpec defines the desired state of CronJob`,\n\t\t`// +kubebuilder:docs-gen:collapse=Imports\n\n/*\nWe'll leave our spec largely unchanged, except to change the schedule field to a new type.\n*/\n// CronJobSpec defines the desired state of CronJob`,\n\t)\n\thackutils.CheckError(\"replace doc about CronjobSpec v2\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html`,\n\t\t`// schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.\n\t// +required\n\tSchedule CronSchedule `+\"`json:\\\"schedule\\\"`\"+`\n\n\t/*\n\t */\n`,\n\t)\n\thackutils.CheckError(\"add new schedule spec type\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`// foo is an example field of CronJob. Edit cronjob_types.go to remove/update\n\t// +optional\n\tFoo *string `+\"`json:\\\"foo,omitempty\\\"`\",\n\t\tcronjobSpecMore,\n\t)\n\thackutils.CheckError(\"replace Foo with cronjob spec fields\", err)\n\n\terr = pluginutil.ReplaceInFile(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`)\n\n}`,\n\t\t`)\n`,\n\t)\n\thackutils.CheckError(\"replace Foo with cronjob spec fields\", err)\n\n\terr = pluginutil.InsertCode(\n\t\tfilepath.Join(sp.ctx.Dir, path),\n\t\t`type CronJobStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file`,\n\t\t`\n\t// active defines a list of pointers to currently running jobs.\n\t// +optional\n\t// +listType=atomic\n\t// +kubebuilder:validation:MinItems=1\n\t// +kubebuilder:validation:MaxItems=10\n\tActive []corev1.ObjectReference `+\"`json:\\\"active,omitempty\\\"`\"+`\n\n\t// lastScheduleTime defines the information when was the last time the job was successfully scheduled.\n\t// +optional\n\tLastScheduleTime *metav1.Time `+\"`json:\\\"lastScheduleTime,omitempty\\\"`\"+`\n`)\n\thackutils.CheckError(\"insert status for cronjob v2\", err)\n\n\terr = pluginutil.AppendCodeAtTheEnd(\n\t\tfilepath.Join(sp.ctx.Dir, path), `\n\t// +kubebuilder:docs-gen:collapse=Other Types`)\n\thackutils.CheckError(\"append marker at the end of the docs\", err)\n}\n\n// CodeGen will call targets to generate code\nfunc (sp *Sample) CodeGen() {\n\tcmd := exec.Command(\"make\", \"all\")\n\t_, err := sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make all for multiversion tutorial\", err)\n\n\tcmd = exec.Command(\"make\", \"build-installer\")\n\t_, err = sp.ctx.Run(cmd)\n\thackutils.CheckError(\"Failed to run make build-installer for  multiversion tutorial\", err)\n\n\terr = sp.ctx.EditHelmPlugin()\n\thackutils.CheckError(\"Failed to enable helm plugin\", err)\n}\n\nconst webhookConversionE2ETest = `\n\t\tIt(\"should successfully convert between v1 and v2 versions\", func() {\n\t\t\tBy(\"waiting for the webhook service to be ready\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpoints\", \"-n\", namespace, \n\t\t\t\t\t\"-l\", \"control-plane=controller-manager\", \n\t\t\t\t\t\"-o\", \"jsonpath={.items[0].subsets[0].addresses[0].ip}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get webhook service endpoints\")\n\t\t\t\tg.Expect(strings.TrimSpace(output)).NotTo(BeEmpty(), \"Webhook endpoint should have an IP\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"creating a v1 CronJob with a specific schedule\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", \"config/samples/batch_v1_cronjob.yaml\", \"-n\", namespace)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create v1 CronJob\")\n\n\t\tBy(\"waiting for the v1 CronJob to be created\")\n\t\tEventually(func(g Gomega) {\n\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.batch.tutorial.kubebuilder.io\", \"cronjob-sample\", \"-n\", namespace)\n\t\t\toutput, err := utils.Run(cmd)\n\t\t\tif err != nil {\n\t\t\t\t// Log controller logs on failure for debugging\n\t\t\t\tlogCmd := exec.Command(\"kubectl\", \"logs\", \"-l\", \"control-plane=controller-manager\", \"-n\", namespace, \"--tail=50\")\n\t\t\t\tlogs, _ := utils.Run(logCmd)\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs when CronJob not found:\\n%s\\n\", logs)\n\t\t\t}\n\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"v1 CronJob should exist, output: \"+output)\n\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\tBy(\"fetching the v1 CronJob and verifying the schedule format\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"cronjob.v1.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule}\")\n\t\t\tv1Schedule, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to get v1 CronJob schedule\")\n\t\t\tExpect(strings.TrimSpace(v1Schedule)).To(Equal(\"*/1 * * * *\"),\n\t\t\t\t\"v1 schedule should be in cron format\")\n\n\t\t\tBy(\"fetching the same CronJob as v2 and verifying the converted schedule\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v2.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule.minute}\")\n\t\t\t\tv2Minute, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get v2 CronJob schedule\")\n\t\t\t\tg.Expect(strings.TrimSpace(v2Minute)).To(Equal(\"*/1\"),\n\t\t\t\t\t\"v2 schedule.minute should be converted from v1 schedule\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"creating a v2 CronJob with structured schedule fields\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"apply\", \"-f\", \"config/samples/batch_v2_cronjob.yaml\", \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create v2 CronJob\")\n\n\t\t\tBy(\"verifying the v2 CronJob has the correct structured schedule\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v2.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule.minute}\")\n\t\t\t\tv2Minute, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get v2 CronJob schedule\")\n\t\t\t\tg.Expect(strings.TrimSpace(v2Minute)).To(Equal(\"*/1\"),\n\t\t\t\t\t\"v2 CronJob should have minute field set\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"fetching the v2 CronJob as v1 and verifying schedule conversion\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"cronjob.v1.batch.tutorial.kubebuilder.io\", \"cronjob-sample\",\n\t\t\t\t\t\"-n\", namespace, \"-o\", \"jsonpath={.spec.schedule}\")\n\t\t\t\tv1Schedule, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to get converted v1 schedule\")\n\t\t\t\t// When v2 only has minute field set, it converts to \"*/1 * * * *\"\n\t\t\t\tg.Expect(strings.TrimSpace(v1Schedule)).To(Equal(\"*/1 * * * *\"),\n\t\t\t\t\t\"v1 schedule should be converted from v2 structured schedule\")\n\t\t\t}, time.Minute, time.Second).Should(Succeed())\n\t\t})`\n\nfunc (sp *Sample) updateE2EWebhookConversion() {\n\tcronjobE2ETest := filepath.Join(sp.ctx.Dir, \"test\", \"e2e\", \"e2e_test.go\")\n\n\t// Add strings import if not already present\n\terr := pluginutil.InsertCodeIfNotExist(cronjobE2ETest,\n\t\t`\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"`,\n\t\t`\n\t\"strings\"`)\n\thackutils.CheckError(\"adding strings import for e2e test\", err)\n\n\t// Add CronJob cleanup to the AfterEach block\n\terr = pluginutil.InsertCode(cronjobE2ETest,\n\t\t`\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {`,\n\t\t`\n\t\tBy(\"Cleaning up test CronJob resources\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", \"config/samples/batch_v1_cronjob.yaml\", \"-n\", namespace, \"--ignore-not-found=true\")\n\t\t_, _ = utils.Run(cmd)\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"-f\", \"config/samples/batch_v2_cronjob.yaml\", \"-n\", namespace, \"--ignore-not-found=true\")\n\t\t_, _ = utils.Run(cmd)\n`)\n\thackutils.CheckError(\"adding CronJob cleanup to AfterEach\", err)\n\n\t// Add webhook conversion test after the existing TODO comment\n\terr = pluginutil.InsertCode(cronjobE2ETest,\n\t\t`\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`+\"`\"+`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`+\"`\"+`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))`,\n\t\twebhookConversionE2ETest)\n\thackutils.CheckError(\"adding webhook conversion e2e test\", err)\n}\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/hub.go",
    "content": "/*\n\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst hubV1CodeComment = `\n/*\nImplementing the hub method is pretty easy -- we just have to add an empty\nmethod called ` + \"`\" + `Hub()` + \"`\" + `to serve as a\n[marker](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc#Hub).\nWe could also just put this inline in our cronjob_types.go file.\n*/\n`\n\nconst hubV2CodeComment = `package v2\n\n/*\nFor imports, we'll need the controller-runtime\n[` + \"`\" + `conversion` + \"`\" + `](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion?tab=doc)\npackage, plus the API version for our hub type (v1), and finally some of the\nstandard packages.\n*/\n`\n\nconst hubV2CovertTo = `sched := src.Spec.Schedule\n\tscheduleParts := []string{\"*\", \"*\", \"*\", \"*\", \"*\"}\n\tif sched.Minute != nil {\n\t\tscheduleParts[0] = string(*sched.Minute)\n\t}\n\tif sched.Hour != nil {\n\t\tscheduleParts[1] = string(*sched.Hour)\n\t}\n\tif sched.DayOfMonth != nil {\n\t\tscheduleParts[2] = string(*sched.DayOfMonth)\n\t}\n\tif sched.Month != nil {\n\t\tscheduleParts[3] = string(*sched.Month)\n\t}\n\tif sched.DayOfWeek != nil {\n\t\tscheduleParts[4] = string(*sched.DayOfWeek)\n\t}\n\tdst.Spec.Schedule = strings.Join(scheduleParts, \" \")\n\n\t/*\n\t\tThe rest of the conversion is pretty rote.\n\t*/\n\t// ObjectMeta\n\tdst.ObjectMeta = src.ObjectMeta\n\n\t// Spec\n\tdst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds\n\tdst.Spec.ConcurrencyPolicy = batchv1.ConcurrencyPolicy(src.Spec.ConcurrencyPolicy)\n\tdst.Spec.Suspend = src.Spec.Suspend\n\tdst.Spec.JobTemplate = src.Spec.JobTemplate\n\tdst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit\n\tdst.Spec.FailedJobsHistoryLimit = src.Spec.FailedJobsHistoryLimit\n\n\t// Status\n\tdst.Status.Active = src.Status.Active\n\tdst.Status.LastScheduleTime = src.Status.LastScheduleTime\n\n\treturn nil\n}\n// +kubebuilder:docs-gen:collapse=rote conversion`\n\nconst hubV2ConvertFromCode = `schedParts := strings.Split(src.Spec.Schedule, \" \")\n\tif len(schedParts) != 5 {\n\t\treturn fmt.Errorf(\"invalid schedule: not a standard 5-field schedule\")\n\t}\n\tpartIfNeeded := func(raw string) *CronField {\n\t\tif raw == \"*\" {\n\t\t\treturn nil\n\t\t}\n\t\tpart := CronField(raw)\n\t\treturn &part\n\t}\n\tdst.Spec.Schedule.Minute = partIfNeeded(schedParts[0])\n\tdst.Spec.Schedule.Hour = partIfNeeded(schedParts[1])\n\tdst.Spec.Schedule.DayOfMonth = partIfNeeded(schedParts[2])\n\tdst.Spec.Schedule.Month = partIfNeeded(schedParts[3])\n\tdst.Spec.Schedule.DayOfWeek = partIfNeeded(schedParts[4])\n\n\t/*\n\t\tThe rest of the conversion is pretty rote.\n\t*/\n\t// ObjectMeta\n\tdst.ObjectMeta = src.ObjectMeta\n\n\t// Spec\n\tdst.Spec.StartingDeadlineSeconds = src.Spec.StartingDeadlineSeconds\n\tdst.Spec.ConcurrencyPolicy = ConcurrencyPolicy(src.Spec.ConcurrencyPolicy)\n\tdst.Spec.Suspend = src.Spec.Suspend\n\tdst.Spec.JobTemplate = src.Spec.JobTemplate\n\tdst.Spec.SuccessfulJobsHistoryLimit = src.Spec.SuccessfulJobsHistoryLimit\n\tdst.Spec.FailedJobsHistoryLimit = src.Spec.FailedJobsHistoryLimit\n\n\t// Status\n\tdst.Status.Active = src.Status.Active\n\tdst.Status.LastScheduleTime = src.Status.LastScheduleTime\n\t\n\treturn nil\n}\n// +kubebuilder:docs-gen:collapse=rote conversion`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/samples.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst sampleV2Code = `schedule:\n    minute: \"*/1\"\n  startingDeadlineSeconds: 60\n  concurrencyPolicy: Allow # explicitly specify, but Allow is also default.\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          securityContext:\n            runAsNonRoot: true\n            runAsUser: 1000\n            seccompProfile:\n              type: RuntimeDefault\n          containers:\n          - name: hello\n            image: busybox\n            args:\n            - /bin/sh\n            - -c\n            - date; echo Hello from the Kubernetes cluster\n            securityContext:\n              allowPrivilegeEscalation: false\n              capabilities:\n                drop:\n                - ALL\n              readOnlyRootFilesystem: false\n          restartPolicy: OnFailure\n`\n"
  },
  {
    "path": "hack/docs/internal/multiversion-tutorial/webhook_v2_implementaton.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage multiversion\n\nconst cronJobFieldsForDefaulting = `\t// Default values for various CronJob fields\n\tDefaultConcurrencyPolicy          batchv2.ConcurrencyPolicy\n\tDefaultSuspend                    bool\n\tDefaultSuccessfulJobsHistoryLimit int32\n\tDefaultFailedJobsHistoryLimit     int32\n`\n\nconst cronJobDefaultingLogic = `// Set default values\n\td.applyDefaults(obj)\n\treturn nil\n`\n\nconst cronJobDefaultFunction = `\n// applyDefaults applies default values to CronJob fields.\nfunc (d *CronJobCustomDefaulter) applyDefaults(cronJob *batchv2.CronJob) {\n\tif cronJob.Spec.ConcurrencyPolicy == \"\" {\n\t\tcronJob.Spec.ConcurrencyPolicy = d.DefaultConcurrencyPolicy\n\t}\n\tif cronJob.Spec.Suspend == nil {\n\t\tcronJob.Spec.Suspend = new(bool)\n\t\t*cronJob.Spec.Suspend = d.DefaultSuspend\n\t}\n\tif cronJob.Spec.SuccessfulJobsHistoryLimit == nil {\n\t\tcronJob.Spec.SuccessfulJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.SuccessfulJobsHistoryLimit = d.DefaultSuccessfulJobsHistoryLimit\n\t}\n\tif cronJob.Spec.FailedJobsHistoryLimit == nil {\n\t\tcronJob.Spec.FailedJobsHistoryLimit = new(int32)\n\t\t*cronJob.Spec.FailedJobsHistoryLimit = d.DefaultFailedJobsHistoryLimit\n\t}\n}\n`\n\nconst cronJobValidationFunction = `\n// +kubebuilder:docs-gen:collapse=Webhook Setup and Defaulting\n\n// validateCronJob validates the fields of a CronJob object.\nfunc validateCronJob(cronjob *batchv2.CronJob) error {\n\tvar allErrs field.ErrorList\n\tif err := validateCronJobName(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif err := validateCronJobSpec(cronjob); err != nil {\n\t\tallErrs = append(allErrs, err)\n\t}\n\tif len(allErrs) == 0 {\n\t\treturn nil\n\t}\n\treturn apierrors.NewInvalid(schema.GroupKind{Group: \"batch.tutorial.kubebuilder.io\", Kind: \"CronJob\"}, cronjob.Name, allErrs)\n}\n\nfunc validateCronJobName(cronjob *batchv2.CronJob) *field.Error {\n\tif len(cronjob.Name) > validationutils.DNS1035LabelMaxLength-11 {\n\t\treturn field.Invalid(field.NewPath(\"metadata\").Child(\"name\"), cronjob.Name, \"must be no more than 52 characters\")\n\t}\n\treturn nil\n}\n\n// validateCronJobSpec validates the schedule format of the custom CronSchedule type\nfunc validateCronJobSpec(cronjob *batchv2.CronJob) *field.Error {\n\t// Build cron expression from the parts\n\tparts := []string{\"*\", \"*\", \"*\", \"*\", \"*\"} // default parts for minute, hour, day of month, month, day of week\n\tif cronjob.Spec.Schedule.Minute != nil {\n\t\tparts[0] = string(*cronjob.Spec.Schedule.Minute)  // Directly cast CronField (which is an alias of string) to string\n\t}\n\tif cronjob.Spec.Schedule.Hour != nil {\n\t\tparts[1] = string(*cronjob.Spec.Schedule.Hour)\n\t}\n\tif cronjob.Spec.Schedule.DayOfMonth != nil {\n\t\tparts[2] = string(*cronjob.Spec.Schedule.DayOfMonth)\n\t}\n\tif cronjob.Spec.Schedule.Month != nil {\n\t\tparts[3] = string(*cronjob.Spec.Schedule.Month)\n\t}\n\tif cronjob.Spec.Schedule.DayOfWeek != nil {\n\t\tparts[4] = string(*cronjob.Spec.Schedule.DayOfWeek)\n\t}\n\n\t// Join parts to form the full cron expression\n\tcronExpression := strings.Join(parts, \" \")\n\n\treturn validateScheduleFormat(\n\t\tcronExpression,\n\t\tfield.NewPath(\"spec\").Child(\"schedule\"))\n}\n\nfunc validateScheduleFormat(schedule string, fldPath *field.Path) *field.Error {\n\tif _, err := cron.ParseStandard(schedule); err != nil {\n\t\treturn field.Invalid(fldPath, schedule, \"invalid cron schedule format: \"+err.Error())\n\t}\n\treturn nil\n}\n`\n\nconst originalSetupManager = `// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.\nfunc SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &batchv2.CronJob{}).\n\t\tWithValidator(&CronJobCustomValidator{}).\n\t\tWithDefaulter(&CronJobCustomDefaulter{}).\n\t\tComplete()\n}`\n\nconst replaceSetupManager = `// SetupCronJobWebhookWithManager registers the webhook for CronJob in the manager.\nfunc SetupCronJobWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &batchv2.CronJob{}).\n\t\tWithValidator(&CronJobCustomValidator{}).\n\t\tWithDefaulter(&CronJobCustomDefaulter{\n\t\t\tDefaultConcurrencyPolicy:          batchv2.AllowConcurrent,\n\t\t\tDefaultSuspend:                    false,\n\t\t\tDefaultSuccessfulJobsHistoryLimit: 3,\n\t\t\tDefaultFailedJobsHistoryLimit:     1,\n\t\t}).\n\t\tComplete()\n}`\n"
  },
  {
    "path": "hack/docs/internal/utils/utils.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\tlog \"log/slog\"\n\t\"os\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// CheckError will exit with exit code 1 when err is not nil.\nfunc CheckError(msg string, err error) {\n\tif err != nil {\n\t\tlog.Error(\"error occurred\", \"message\", msg, \"error\", err)\n\t\tos.Exit(1)\n\t}\n}\n\n// NewSampleContext return a context for the Sample\nfunc NewSampleContext(binaryPath string, samplePath string, env ...string) utils.TestContext {\n\tcmdContext := &utils.CmdContext{\n\t\tEnv: env,\n\t\tDir: samplePath,\n\t}\n\n\ttestContext := utils.TestContext{\n\t\tCmdContext: cmdContext,\n\t\tBinaryName: binaryPath,\n\t}\n\n\treturn testContext\n}\n"
  },
  {
    "path": "hack/test/check_go_module.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Go module sanity checker validates module compatibility before release:\n// 1. Validates file paths with x/mod/module.CheckFilePath\n// 2. Ensures required retracted versions are present in go.mod\n// 3. Reads module path and Go version from go.mod\n// 4. Creates a consumer module to test installability\n// 5. Runs `go mod tidy` and `go build ./...` to verify module works\n//\n// This prevents releasing tags that break `go install`.\n//\n// Run with:\n//   go run ./hack/test/check_go_module.go\n\npackage main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"golang.org/x/mod/module\"\n)\n\nfunc main() {\n\tif err := checkFilePaths(); err != nil {\n\t\tlog.Error(\"file path validation failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif err := checkRetractedVersions(); err != nil {\n\t\tlog.Error(\"retracted version check failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tmodulePath, goVersion, err := readGoModInfo()\n\tif err != nil {\n\t\tlog.Error(\"failed to read go.mod\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tif err := setupAndCheckConsumer(modulePath, goVersion); err != nil {\n\t\tlog.Error(\"consumer module validation failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\n\tlog.Info(\"Go module compatibility check passed\")\n}\n\nfunc checkFilePaths() error {\n\tlog.Info(\"Checking Go module file paths\")\n\n\tout, err := exec.Command(\"git\", \"ls-files\").Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list git tracked files: %w\", err)\n\t}\n\n\tvar invalidPaths []string\n\tfor line := range strings.SplitSeq(string(out), \"\\n\") {\n\t\tpath := strings.TrimSpace(line)\n\t\tif path == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := module.CheckFilePath(path); err != nil {\n\t\t\tinvalidPaths = append(invalidPaths, fmt.Sprintf(\"  %q: %v\", path, err))\n\t\t}\n\t}\n\n\tif len(invalidPaths) > 0 {\n\t\tvar buf bytes.Buffer\n\t\tbuf.WriteString(\"invalid file paths found:\\n\")\n\t\tfor _, p := range invalidPaths {\n\t\t\tbuf.WriteString(p)\n\t\t\tbuf.WriteByte('\\n')\n\t\t}\n\t\treturn fmt.Errorf(\"%s\", buf.String())\n\t}\n\n\tlog.Info(\"File path validation passed\")\n\treturn nil\n}\n\nfunc checkRetractedVersions() error {\n\tlog.Info(\"Checking for required retracted versions in go.mod\")\n\n\tcontent, err := os.ReadFile(\"go.mod\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read go.mod: %w\", err)\n\t}\n\n\trequiredRetractions := []string{\n\t\t\"retract v4.10.0\", // invalid filename causes go get/install failure (#5211)\n\t}\n\n\tfor _, retract := range requiredRetractions {\n\t\tif !strings.Contains(string(content), retract) {\n\t\t\treturn fmt.Errorf(\"missing required retraction: %s\", retract)\n\t\t}\n\t}\n\n\tlog.Info(\"Retracted versions check passed\")\n\treturn nil\n}\n\nfunc readGoModInfo() (modulePath, goVersion string, err error) {\n\tlog.Info(\"Reading module info from go.mod\")\n\n\tf, openErr := os.Open(\"go.mod\")\n\tif openErr != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"failed to open go.mod: %w\", openErr)\n\t}\n\tdefer func() {\n\t\tif closeErr := f.Close(); closeErr != nil {\n\t\t\tlog.Warn(\"failed to close go.mod\", \"error\", closeErr)\n\t\t}\n\t}()\n\n\tsc := bufio.NewScanner(f)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\n\t\t// Read module path from first line\n\t\tif after, ok := strings.CutPrefix(line, \"module \"); ok {\n\t\t\tmodulePath = strings.TrimSpace(after)\n\t\t\tlog.Info(\"Found module path\", \"module\", modulePath)\n\t\t}\n\n\t\t// Read Go version\n\t\tif after, ok := strings.CutPrefix(line, \"go \"); ok {\n\t\t\tgoVersion = strings.TrimSpace(after)\n\t\t\tlog.Info(\"Found Go version\", \"version\", goVersion)\n\t\t}\n\n\t\t// Stop once we have both\n\t\tif modulePath != \"\" && goVersion != \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif modulePath == \"\" {\n\t\treturn \"\", \"\", fmt.Errorf(\"no 'module' directive found in go.mod\")\n\t}\n\tif goVersion == \"\" {\n\t\treturn \"\", \"\", fmt.Errorf(\"no 'go' directive found in go.mod\")\n\t}\n\n\treturn modulePath, goVersion, nil\n}\n\nfunc setupAndCheckConsumer(modulePath, goVersion string) error {\n\tlog.Info(\"Creating consumer module\", \"module\", modulePath, \"go_version\", goVersion)\n\n\t// Create temporary directory under hack/test/ (covered by **/e2e-*/** in .gitignore)\n\tconsumerDir := filepath.Join(\"hack\", \"test\", \"e2e-module-check\")\n\tif err := os.MkdirAll(consumerDir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create temp dir: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err := os.RemoveAll(consumerDir); err != nil {\n\t\t\tlog.Warn(\"failed to cleanup temp dir\", \"dir\", consumerDir, \"error\", err)\n\t\t}\n\t}()\n\n\tif err := writeConsumerFiles(consumerDir, modulePath, goVersion); err != nil {\n\t\treturn err\n\t}\n\n\tlog.Info(\"Running go mod tidy in consumer module\")\n\tif err := runCommand(consumerDir, \"go\", \"mod\", \"tidy\"); err != nil {\n\t\treturn fmt.Errorf(\"go mod tidy failed: %w\", err)\n\t}\n\n\tlog.Info(\"Building consumer module\")\n\tif err := runCommand(consumerDir, \"go\", \"build\", \"./...\"); err != nil {\n\t\treturn fmt.Errorf(\"go build failed: %w\", err)\n\t}\n\n\tlog.Info(\"Consumer module build succeeded\")\n\treturn nil\n}\n\nfunc writeConsumerFiles(consumerDir, modulePath, goVersion string) error {\n\tgoMod := fmt.Sprintf(`module module-consumer\n\ngo %s\n\nrequire %s v4.0.0-00010101000000-000000000000\n\nreplace %s => ../../..\n`, goVersion, modulePath, modulePath)\n\n\t// Use a basic import from the module to verify it can be consumed\n\tmainGo := fmt.Sprintf(`package main\n\nimport (\n\t_ \"%s/pkg/plugins/golang/v4\"\n)\n\nfunc main() {}\n`, modulePath)\n\n\tif err := os.WriteFile(filepath.Join(consumerDir, \"go.mod\"), []byte(goMod), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write consumer go.mod: %w\", err)\n\t}\n\n\tif err := os.WriteFile(filepath.Join(consumerDir, \"main.go\"), []byte(mainGo), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write consumer main.go: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// runCommand executes a command in the specified directory with stdout/stderr connected\nfunc runCommand(dir, name string, args ...string) error {\n\tcmd := exec.Command(name, args...)\n\tcmd.Dir = dir\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"command %s failed in %s: %w\", name, dir, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/generate.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\thttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage alpha\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal\"\n)\n\n// NewScaffoldCommand returns a new scaffold command, providing the `kubebuilder alpha generate`\n// feature to re-scaffold projects and assist users with updates.\n//\n// IMPORTANT: This command is intended solely for Kubebuilder's use, as it is designed to work\n// specifically within Kubebuilder's project configuration, key mappings, and plugin initialization.\n// Its implementation includes fixed values and logic tailored to Kubebuilder’s unique setup, which may\n// not apply to other projects. Consequently, importing and using this command directly in other projects\n// will likely result in unexpected behavior, as external projects may have different supported plugin\n// structures, configurations, and requirements.\n//\n// For other projects using Kubebuilder as a library, replicating similar functionality would require\n// a custom implementation to ensure compatibility with the specific configurations and plugins of that project.\n//\n// Technically, implementing functions that allow re-scaffolding with the exact plugins and project-specific\n// code of external projects is not feasible within Kubebuilder’s current design.\nfunc NewScaffoldCommand() *cobra.Command {\n\topts := internal.Generate{}\n\n\tscaffoldCmd := &cobra.Command{\n\t\tUse:   \"generate\",\n\t\tShort: \"Re-scaffold a Kubebuilder project from its PROJECT file\",\n\t\tLong: `The 'generate' command re-creates a Kubebuilder project scaffold based on the configuration \ndefined in the PROJECT file, using the latest installed Kubebuilder version and plugins.\n\nThis is helpful for migrating projects to a newer Kubebuilder layout or plugin version (e.g., v3 to v4)\nas update your project from any previous version to the current one.\n\nIf no output directory is provided, the current working directory will be cleaned (except .git and PROJECT).`,\n\t\tExample: `\n  # **WARNING**(will delete all files to allow the re-scaffold except .git and PROJECT)\n  # Re-scaffold the project in-place \n  kubebuilder alpha generate\n\n  # Re-scaffold the project from ./test into ./my-output\n  kubebuilder alpha generate --input-dir=\"./path/to/project\" --output-dir=\"./my-output\"\n`,\n\t\tPreRunE: func(_ *cobra.Command, _ []string) error {\n\t\t\treturn opts.Validate()\n\t\t},\n\t\tRun: func(_ *cobra.Command, _ []string) {\n\t\t\tif err := opts.Generate(); err != nil {\n\t\t\t\tslog.Error(\"failed to generate project\", \"error\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t},\n\t}\n\n\tscaffoldCmd.Flags().StringVar(&opts.InputDir, \"input-dir\", \"\",\n\t\t\"Path to the directory containing the PROJECT file. \"+\n\t\t\t\"Defaults to the current working directory. WARNING: delete existing files (except .git and PROJECT).\")\n\n\tscaffoldCmd.Flags().StringVar(&opts.OutputDir, \"output-dir\", \"\",\n\t\t\"Directory where the new project scaffold will be written. \"+\n\t\t\t\"If unset, re-scaffolding occurs in-place \"+\n\t\t\t\"and will delete existing files (except .git and PROJECT).\")\n\n\treturn scaffoldCmd\n}\n"
  },
  {
    "path": "internal/cli/alpha/generate_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\thttp://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//lint:ignore ST1001 we use dot-imports in tests for brevity\n\npackage alpha\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"NewScaffoldCommand\", func() {\n\tWhen(\"NewScaffoldCommand\", func() {\n\t\tIt(\"Testing the NewScaffoldCommand\", func() {\n\t\t\tcmd := NewScaffoldCommand()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"generate\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Re-scaffold a Kubebuilder project from its PROJECT file\"))\n\t\t\tExpect(cmd.Example).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Example).To(ContainSubstring(\"kubebuilder alpha generate\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/common/common.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// LoadProjectConfig load the project config.\nfunc LoadProjectConfig(inputDir string) (store.Store, error) {\n\tprojectConfig := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()})\n\tif err := projectConfig.LoadFrom(fmt.Sprintf(\"%s/%s\", inputDir, yaml.DefaultPath)); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to load PROJECT file: %w\", err)\n\t}\n\treturn projectConfig, nil\n}\n\n// GetInputPath will return the input path for the project.\nfunc GetInputPath(inputPath string) (string, error) {\n\tif inputPath == \"\" {\n\t\tcwd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to get working directory: %w\", err)\n\t\t}\n\t\tinputPath = cwd\n\t}\n\tprojectPath := fmt.Sprintf(\"%s/%s\", inputPath, yaml.DefaultPath)\n\tif _, err := os.Stat(projectPath); os.IsNotExist(err) {\n\t\treturn \"\", fmt.Errorf(\"project path %q does not exist: %w\", projectPath, err)\n\t}\n\treturn inputPath, nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/common/common_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\tv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nvar _ = Describe(\"LoadProjectConfig\", func() {\n\tvar (\n\t\tkbc         *utils.TestContext\n\t\tprojectFile string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\t\tprojectFile = filepath.Join(kbc.Dir, yaml.DefaultPath)\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"cleaning up test artifacts\")\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"when PROJECT file exists and is valid\", func() {\n\t\tIt(\"should load the project config successfully\", func() {\n\t\t\tconfig.Register(config.Version{Number: 3}, func() config.Config {\n\t\t\t\treturn &v3.Cfg{Version: config.Version{Number: 3}}\n\t\t\t})\n\n\t\t\tconst version = `version: \"3\"\n`\n\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\tcfg, err := LoadProjectConfig(kbc.Dir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg).NotTo(BeNil())\n\t\t})\n\t})\n\n\tContext(\"when PROJECT file does not exist\", func() {\n\t\tIt(\"should return an error\", func() {\n\t\t\t_, err := LoadProjectConfig(kbc.Dir)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to load PROJECT file\"))\n\t\t})\n\t})\n\n\tContext(\"when PROJECT file is invalid\", func() {\n\t\tIt(\"should return an error\", func() {\n\t\t\tExpect(os.WriteFile(projectFile, []byte(\":?!\"), 0o644)).To(Succeed())\n\n\t\t\t_, err := LoadProjectConfig(kbc.Dir)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to load PROJECT file\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"GetInputPath\", func() {\n\tvar (\n\t\tkbc         *utils.TestContext\n\t\tprojectFile string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\t\tprojectFile = filepath.Join(kbc.Dir, yaml.DefaultPath)\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"cleaning up test artifacts\")\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"when inputPath has trailing slash\", func() {\n\t\tIt(\"should handle trailing slash and find PROJECT file\", func() {\n\t\t\tExpect(os.WriteFile(projectFile, []byte(\"test\"), 0o644)).To(Succeed())\n\n\t\t\tinputPath, err := GetInputPath(kbc.Dir + \"/\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(inputPath).To(Equal(kbc.Dir + \"/\"))\n\t\t})\n\t})\n\n\tContext(\"when inputPath is empty\", func() {\n\t\tIt(\"should return error if PROJECT file does not exist in CWD\", func() {\n\t\t\tinputPath, err := GetInputPath(\"\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(inputPath).To(Equal(\"\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"does not exist\"))\n\t\t})\n\t})\n\n\tContext(\"when inputPath is valid and PROJECT file exists\", func() {\n\t\tIt(\"should return the inputPath\", func() {\n\t\t\tExpect(os.WriteFile(projectFile, []byte(\"test\"), 0o644)).To(Succeed())\n\n\t\t\tinputPath, err := GetInputPath(kbc.Dir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(inputPath).To(Equal(kbc.Dir))\n\t\t})\n\t})\n\n\tContext(\"when inputPath is valid but PROJECT file does not exist\", func() {\n\t\tIt(\"should return an error\", func() {\n\t\t\tinputPath, err := GetInputPath(kbc.Dir)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(inputPath).To(Equal(\"\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"does not exist\"))\n\t\t})\n\t})\n\n\tContext(\"when inputPath does not exist\", func() {\n\t\tIt(\"should return an error\", func() {\n\t\t\tinvalidPath := filepath.Join(kbc.Dir, \"nonexistent\")\n\t\t\tinputPath, err := GetInputPath(invalidPath)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(inputPath).To(Equal(\"\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"does not exist\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/common/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage common\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestCommon(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Common Package Suite For Alpha Commands\")\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/generate.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage internal\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/common\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\tdeployimagev1alpha1 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\"\n\tautoupdatev1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha\"\n\tgrafanav1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha\"\n\thelmv1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha\"\n\thelmv2alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha\"\n)\n\n// Generate store the required info for the command\ntype Generate struct {\n\tInputDir  string\n\tOutputDir string\n}\n\n// Define a variable to allow overriding the behavior of getExecutablePath for testing.\nvar getExecutablePathFunc = getExecutablePath\n\n// Generate handles the migration and scaffolding process.\nfunc (opts *Generate) Generate() error {\n\tprojectConfig, err := common.LoadProjectConfig(opts.InputDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error loading project config: %v\", err)\n\t}\n\n\tif opts.OutputDir == \"\" {\n\t\tcwd, getWdErr := os.Getwd()\n\t\tif getWdErr != nil {\n\t\t\treturn fmt.Errorf(\"failed to get working directory: %w\", getWdErr)\n\t\t}\n\t\topts.OutputDir = cwd\n\t\tif _, err = os.Stat(opts.OutputDir); err == nil {\n\t\t\tslog.Warn(\"Using current working directory to re-scaffold the project\")\n\t\t\tslog.Warn(\"This directory will be cleaned up and all files removed before the re-generation\")\n\n\t\t\t// Ensure we clean the correct directory\n\t\t\tslog.Info(\"Cleaning directory\", \"dir\", opts.OutputDir)\n\n\t\t\t// Use an absolute path to target files directly\n\t\t\tcleanupCmd := fmt.Sprintf(\"rm -rf %s/*\", opts.OutputDir)\n\t\t\terr = util.RunCmd(\"Running cleanup\", \"sh\", \"-c\", cleanupCmd)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"Cleanup failed\", \"error\", err)\n\t\t\t\treturn fmt.Errorf(\"cleanup failed: %w\", err)\n\t\t\t}\n\n\t\t\t// Note that we should remove ALL files except the PROJECT file and .git directory\n\t\t\tcleanupCmd = fmt.Sprintf(\n\t\t\t\t`find %q -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +`,\n\t\t\t\topts.OutputDir,\n\t\t\t)\n\t\t\terr = util.RunCmd(\"Running cleanup\", \"sh\", \"-c\", cleanupCmd)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"Cleanup failed\", \"error\", err)\n\t\t\t\treturn fmt.Errorf(\"cleanup failed: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif err = createDirectory(opts.OutputDir); err != nil {\n\t\treturn fmt.Errorf(\"error creating output directory %q: %w\", opts.OutputDir, err)\n\t}\n\n\tif err = changeWorkingDirectory(opts.OutputDir); err != nil {\n\t\treturn fmt.Errorf(\"error changing working directory %q: %w\", opts.OutputDir, err)\n\t}\n\n\tif err = kubebuilderInit(projectConfig); err != nil {\n\t\treturn fmt.Errorf(\"error initializing project config: %w\", err)\n\t}\n\n\tif err = kubebuilderCreate(projectConfig); err != nil {\n\t\treturn fmt.Errorf(\"error creating project config: %w\", err)\n\t}\n\n\tif err = migrateGrafanaPlugin(projectConfig, opts.InputDir, opts.OutputDir); err != nil {\n\t\treturn fmt.Errorf(\"error migrating Grafana plugin: %w\", err)\n\t}\n\n\tif err = migrateAutoUpdatePlugin(projectConfig); err != nil {\n\t\treturn fmt.Errorf(\"error migrating AutoUpdate plugin: %w\", err)\n\t}\n\n\tif hasHelm, isV2Alpha := hasHelmPlugin(projectConfig); hasHelm && isV2Alpha {\n\t\tif err = kubebuilderHelmEditWithConfig(projectConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"error editing Helm plugin: %w\", err)\n\t\t}\n\t}\n\n\tif err = migrateDeployImagePlugin(projectConfig); err != nil {\n\t\treturn fmt.Errorf(\"error migrating deploy-image plugin: %w\", err)\n\t}\n\n\t// Run make targets to ensure the project is properly set up.\n\t// These steps are performed on a best-effort basis: if any of the targets fail,\n\t// we slog a warning to inform the user, but we do not stop the process or return an error.\n\t// This is to avoid blocking the migration flow due to non-critical issues during setup.\n\ttargets := []string{\"fmt\", \"vet\", \"lint-fix\"}\n\tfor _, target := range targets {\n\t\terr := util.RunCmd(fmt.Sprintf(\"Running make %s\", target), \"make\", target)\n\t\tif err != nil {\n\t\t\tslog.Warn(\"make target failed\", \"target\", target, \"error\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Validate ensures the options are valid and kubebuilder is installed.\nfunc (opts *Generate) Validate() error {\n\tvar err error\n\topts.InputDir, err = common.GetInputPath(opts.InputDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting input path %q: %w\", opts.InputDir, err)\n\t}\n\n\t_, err = getExecutablePathFunc()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Helper function to get the PATH of binary.\nfunc getExecutablePath() (string, error) {\n\texecPath, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"kubebuilder executable not found: %w\", err)\n\t}\n\n\trealPath, err := filepath.EvalSymlinks(execPath)\n\tif err != nil {\n\t\tslog.Warn(\"Unable to resolve symbolic link\", \"execPath\", execPath, \"error\", err)\n\t\t// Fallback to execPath\n\t\treturn execPath, nil\n\t}\n\n\treturn realPath, nil\n}\n\n// Helper function to create the output directory.\nfunc createDirectory(outputDir string) error {\n\tif err := os.MkdirAll(outputDir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create output directory %q: %w\", outputDir, err)\n\t}\n\treturn nil\n}\n\n// Helper function to change the current working directory.\nfunc changeWorkingDirectory(outputDir string) error {\n\tif err := os.Chdir(outputDir); err != nil {\n\t\treturn fmt.Errorf(\"failed to change the working directory to %q: %w\", outputDir, err)\n\t}\n\treturn nil\n}\n\n// Initializes the project with Kubebuilder.\nfunc kubebuilderInit(s store.Store) error {\n\targs := append([]string{\"init\"}, getInitArgs(s)...)\n\texecPath, err := getExecutablePathFunc()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := util.RunCmd(\"kubebuilder init\", execPath, args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run kubebuilder init command: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Creates APIs and Webhooks for the project.\nfunc kubebuilderCreate(s store.Store) error {\n\tresources, err := s.Config().GetResources()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get resources: %w\", err)\n\t}\n\n\t// First, scaffold all APIs\n\tfor _, r := range resources {\n\t\tif err = createAPI(r); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create API for %s/%s/%s: %w\", r.Group, r.Version, r.Kind, err)\n\t\t}\n\t}\n\n\t// Then, scaffold all webhooks\n\t// We cannot create a webhook for an API that does not exist\n\tfor _, r := range resources {\n\t\tif err = createWebhook(r); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create webhook for %s/%s/%s: %w\", r.Group, r.Version, r.Kind, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Migrates the Grafana plugin.\nfunc migrateGrafanaPlugin(s store.Store, src, des string) error {\n\tvar grafanaPlugin struct{}\n\tkey := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), grafanav1alpha.Plugin{})\n\tcanonicalKey := plugin.KeyFor(grafanav1alpha.Plugin{})\n\tfound := true\n\tvar err error\n\n\tif err = s.Config().DecodePluginConfig(key, grafanaPlugin); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tfound = false\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err = s.Config().DecodePluginConfig(canonicalKey, grafanaPlugin); err != nil {\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\t\t\t\t// still not found\n\t\t\t\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\t\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Grafana migration\")\n\t\t\t\t\t\treturn nil\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to decode grafana plugin config: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Grafana migration\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"failed to decode grafana plugin config: %w\", err)\n\t\t}\n\t}\n\n\tif !found {\n\t\tslog.Info(\"Grafana plugin not found, skipping migration\")\n\t\treturn nil\n\t}\n\n\tif err = kubebuilderGrafanaEdit(); err != nil {\n\t\treturn fmt.Errorf(\"error editing Grafana plugin: %w\", err)\n\t}\n\n\tif err = grafanaConfigMigrate(src, des); err != nil {\n\t\treturn fmt.Errorf(\"error migrating Grafana config: %w\", err)\n\t}\n\n\treturn kubebuilderGrafanaEdit()\n}\n\nfunc migrateAutoUpdatePlugin(s store.Store) error {\n\tkey := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), autoupdatev1alpha.Plugin{})\n\tcanonicalKey := plugin.KeyFor(autoupdatev1alpha.Plugin{})\n\tvar autoUpdatePlugin autoupdatev1alpha.PluginConfig\n\tfound := true\n\n\tvar err error\n\terr = s.Config().DecodePluginConfig(key, &autoUpdatePlugin)\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tfound = false\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err = s.Config().DecodePluginConfig(canonicalKey, &autoUpdatePlugin); err != nil {\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\t\t\t\t// still not found\n\t\t\t\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\t\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Auto Update migration\")\n\t\t\t\t\t\treturn nil\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to decode autoupdate plugin config: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Auto Update migration\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"failed to decode autoupdate plugin config: %w\", err)\n\t\t}\n\t}\n\n\tif !found {\n\t\tslog.Info(\"Auto Update plugin not found, skipping migration\")\n\t\treturn nil\n\t}\n\n\targs := []string{\"edit\", \"--plugins\", plugin.KeyFor(autoupdatev1alpha.Plugin{})}\n\tif autoUpdatePlugin.UseGHModels {\n\t\targs = append(args, \"--use-gh-models\")\n\t}\n\tif err = util.RunCmd(\"kubebuilder edit\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run edit subcommand for Auto plugin: %w\", err)\n\t}\n\treturn nil\n}\n\n// Migrates the Deploy Image plugin.\nfunc migrateDeployImagePlugin(s store.Store) error {\n\tkey := plugin.GetPluginKeyForConfig(s.Config().GetPluginChain(), deployimagev1alpha1.Plugin{})\n\tcanonicalKey := plugin.KeyFor(deployimagev1alpha1.Plugin{})\n\tvar deployImagePlugin deployimagev1alpha1.PluginConfig\n\tfound := true\n\n\tvar err error\n\terr = s.Config().DecodePluginConfig(key, &deployImagePlugin)\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tfound = false\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err = s.Config().DecodePluginConfig(canonicalKey, &deployImagePlugin); err != nil {\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\t\t\t\t// still not found\n\t\t\t\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\t\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Deploy Image migration\")\n\t\t\t\t\t\treturn nil\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to decode deploy-image plugin config: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\tslog.Info(\"Project config version does not support plugin metadata, skipping Deploy Image migration\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"failed to decode deploy-image plugin config: %w\", err)\n\t\t}\n\t}\n\n\tif !found {\n\t\tslog.Info(\"Deploy-image plugin not found, skipping migration\")\n\t\treturn nil\n\t}\n\n\tfor _, r := range deployImagePlugin.Resources {\n\t\tif err := createAPIWithDeployImage(r); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create API with deploy-image: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Creates an API with Deploy Image plugin.\nfunc createAPIWithDeployImage(resourceData deployimagev1alpha1.ResourceData) error {\n\targs := append([]string{\"create\", \"api\"}, getGVKFlagsFromDeployImage(resourceData)...)\n\targs = append(args, getDeployImageOptions(resourceData)...)\n\tif err := util.RunCmd(\"kubebuilder create api\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run kubebuilder create api command: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Helper function to get Init arguments for Kubebuilder.\nfunc getInitArgs(s store.Store) []string {\n\tvar args []string\n\tplugins := s.Config().GetPluginChain()\n\n\t// Define outdated plugin versions that need replacement\n\toutdatedPlugins := map[string]string{\n\t\t\"go.kubebuilder.io/v3\":         \"go.kubebuilder.io/v4\",\n\t\t\"go.kubebuilder.io/v3-alpha\":   \"go.kubebuilder.io/v4\",\n\t\t\"go.kubebuilder.io/v2\":         \"go.kubebuilder.io/v4\",\n\t\t\"helm.kubebuilder.io/v1-alpha\": \"helm.kubebuilder.io/v2-alpha\",\n\t}\n\n\t// Replace outdated plugins and exit after the first replacement\n\tfor i, plg := range plugins {\n\t\tif newPlugin, exists := outdatedPlugins[plg]; exists {\n\t\t\tslog.Warn(\"We checked that your PROJECT file is configured with deprecated layout. \"+\n\t\t\t\t\"However, we will try our best to re-generate the project using new one\",\n\t\t\t\t\"deprecated_layout\", plg,\n\t\t\t\t\"new_layout\", newPlugin)\n\t\t\tplugins[i] = newPlugin\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(plugins) > 0 {\n\t\targs = append(args, \"--plugins\", strings.Join(plugins, \",\"))\n\t}\n\tif domain := s.Config().GetDomain(); domain != \"\" {\n\t\targs = append(args, \"--domain\", domain)\n\t}\n\tif repo := s.Config().GetRepository(); repo != \"\" {\n\t\targs = append(args, \"--repo\", repo)\n\t}\n\tif projectName := s.Config().GetProjectName(); projectName != \"\" {\n\t\targs = append(args, \"--project-name\", projectName)\n\t}\n\tif s.Config().IsMultiGroup() {\n\t\targs = append(args, \"--multigroup\")\n\t}\n\tif s.Config().IsNamespaced() {\n\t\targs = append(args, \"--namespaced\")\n\t}\n\treturn args\n}\n\n// Gets the GVK flags for a resource.\nfunc getGVKFlags(res resource.Resource) []string {\n\tvar args []string\n\tif res.Plural != \"\" {\n\t\targs = append(args, \"--plural\", res.Plural)\n\t}\n\tif res.Group != \"\" {\n\t\targs = append(args, \"--group\", res.Group)\n\t}\n\tif res.Version != \"\" {\n\t\targs = append(args, \"--version\", res.Version)\n\t}\n\tif res.Kind != \"\" {\n\t\targs = append(args, \"--kind\", res.Kind)\n\t}\n\treturn args\n}\n\n// Gets the GVK flags for a Deploy Image resource.\nfunc getGVKFlagsFromDeployImage(resourceData deployimagev1alpha1.ResourceData) []string {\n\tvar args []string\n\tif resourceData.Group != \"\" {\n\t\targs = append(args, \"--group\", resourceData.Group)\n\t}\n\tif resourceData.Version != \"\" {\n\t\targs = append(args, \"--version\", resourceData.Version)\n\t}\n\tif resourceData.Kind != \"\" {\n\t\targs = append(args, \"--kind\", resourceData.Kind)\n\t}\n\treturn args\n}\n\n// Gets the options for a Deploy Image resource.\nfunc getDeployImageOptions(resourceData deployimagev1alpha1.ResourceData) []string {\n\tvar args []string\n\tif resourceData.Options.Image != \"\" {\n\t\targs = append(args, fmt.Sprintf(\"--image=%s\", resourceData.Options.Image))\n\t}\n\tif resourceData.Options.ContainerCommand != \"\" {\n\t\targs = append(args, fmt.Sprintf(\"--image-container-command=%s\", resourceData.Options.ContainerCommand))\n\t}\n\tif resourceData.Options.ContainerPort != \"\" {\n\t\targs = append(args, fmt.Sprintf(\"--image-container-port=%s\", resourceData.Options.ContainerPort))\n\t}\n\tif resourceData.Options.RunAsUser != \"\" {\n\t\targs = append(args, fmt.Sprintf(\"--run-as-user=%s\", resourceData.Options.RunAsUser))\n\t}\n\targs = append(args, fmt.Sprintf(\"--plugins=%s\", plugin.KeyFor(deployimagev1alpha1.Plugin{})))\n\treturn args\n}\n\n// Creates an API resource.\nfunc createAPI(res resource.Resource) error {\n\targs := append([]string{\"create\", \"api\"}, getGVKFlags(res)...)\n\targs = append(args, getAPIResourceFlags(res)...)\n\n\t// Add the external API flags if the resource is external\n\tif res.IsExternal() {\n\t\targs = append(args, \"--external-api-path\", res.Path)\n\t\targs = append(args, \"--external-api-domain\", res.Domain)\n\t\t// Add module if specified\n\t\tif res.Module != \"\" {\n\t\t\targs = append(args, \"--external-api-module\", res.Module)\n\t\t}\n\t}\n\n\tif err := util.RunCmd(\"kubebuilder create api\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run kubebuilder create api command: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Gets flags for API resource creation.\nfunc getAPIResourceFlags(res resource.Resource) []string {\n\tvar args []string\n\n\tif res.API == nil || res.API.IsEmpty() {\n\t\targs = append(args, \"--resource=false\")\n\t} else {\n\t\targs = append(args, \"--resource\")\n\t\tif res.API.Namespaced {\n\t\t\targs = append(args, \"--namespaced\")\n\t\t} else {\n\t\t\targs = append(args, \"--namespaced=false\")\n\t\t}\n\t}\n\tif res.Controller {\n\t\targs = append(args, \"--controller\")\n\t} else {\n\t\targs = append(args, \"--controller=false\")\n\t}\n\treturn args\n}\n\n// Creates a webhook resource.\nfunc createWebhook(res resource.Resource) error {\n\tif res.Webhooks == nil || res.Webhooks.IsEmpty() {\n\t\treturn nil\n\t}\n\targs := append([]string{\"create\", \"webhook\"}, getGVKFlags(res)...)\n\targs = append(args, getWebhookResourceFlags(res)...)\n\n\tif err := util.RunCmd(\"kubebuilder create webhook\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run kubebuilder create webhook command: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Gets flags for webhook creation.\nfunc getWebhookResourceFlags(res resource.Resource) []string {\n\tvar args []string\n\tif res.IsExternal() {\n\t\targs = append(args, \"--external-api-path\", res.Path)\n\t\targs = append(args, \"--external-api-domain\", res.Domain)\n\t\t// Add module if specified\n\t\tif res.Module != \"\" {\n\t\t\targs = append(args, \"--external-api-module\", res.Module)\n\t\t}\n\t}\n\tif res.HasValidationWebhook() {\n\t\targs = append(args, \"--programmatic-validation\")\n\t\tif res.Webhooks.ValidationPath != \"\" {\n\t\t\targs = append(args, \"--validation-path\", res.Webhooks.ValidationPath)\n\t\t}\n\t}\n\tif res.HasDefaultingWebhook() {\n\t\targs = append(args, \"--defaulting\")\n\t\tif res.Webhooks.DefaultingPath != \"\" {\n\t\t\targs = append(args, \"--defaulting-path\", res.Webhooks.DefaultingPath)\n\t\t}\n\t}\n\tif res.HasConversionWebhook() {\n\t\targs = append(args, \"--conversion\")\n\t\tif len(res.Webhooks.Spoke) > 0 {\n\t\t\tfor _, spoke := range res.Webhooks.Spoke {\n\t\t\t\targs = append(args, \"--spoke\", spoke)\n\t\t\t}\n\t\t}\n\t\t// Note: conversion webhooks don't use custom path flags\n\t}\n\treturn args\n}\n\n// Copies files from source to destination.\nfunc copyFile(src, des string) error {\n\tbytesRead, err := os.ReadFile(src)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"source file path %q does not exist: %w\", src, err)\n\t}\n\tif err = os.WriteFile(des, bytesRead, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", des, err)\n\t}\n\n\treturn nil\n}\n\n// Migrates Grafana configuration files.\nfunc grafanaConfigMigrate(src, des string) error {\n\tgrafanaConfig := fmt.Sprintf(\"%s/grafana/custom-metrics/config.yaml\", src)\n\tif _, err := os.Stat(grafanaConfig); os.IsNotExist(err) {\n\t\tslog.Info(\"Grafana config file not found, skipping file migration\", \"path\", grafanaConfig)\n\t\treturn nil // Don't fail if config files don't exist\n\t}\n\treturn copyFile(grafanaConfig, fmt.Sprintf(\"%s/grafana/custom-metrics/config.yaml\", des))\n}\n\n// Edits the project to include the Grafana plugin.\nfunc kubebuilderGrafanaEdit() error {\n\targs := []string{\"edit\", \"--plugins\", plugin.KeyFor(grafanav1alpha.Plugin{})}\n\tif err := util.RunCmd(\"kubebuilder edit\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run edit subcommand for Grafana plugin: %w\", err)\n\t}\n\treturn nil\n}\n\n// Edits the project to include the Helm plugin with tracked configuration.\nfunc kubebuilderHelmEditWithConfig(s store.Store) error {\n\tvar cfg struct {\n\t\tManifestsFile string `json:\"manifests,omitempty\"`\n\t\tOutputDir     string `json:\"output,omitempty\"`\n\t}\n\terr := s.Config().DecodePluginConfig(plugin.KeyFor(helmv2alpha.Plugin{}), &cfg)\n\tif errors.As(err, &config.PluginKeyNotFoundError{}) {\n\t\t// No previous configuration, use defaults\n\t\treturn kubebuilderHelmEdit(true)\n\t} else if err != nil {\n\t\treturn fmt.Errorf(\"failed to decode helm plugin config: %w\", err)\n\t}\n\n\t// Use tracked configuration values\n\tpluginKey := plugin.KeyFor(helmv2alpha.Plugin{})\n\targs := []string{\"edit\", \"--plugins\", pluginKey}\n\tif cfg.ManifestsFile != \"\" {\n\t\targs = append(args, \"--manifests\", cfg.ManifestsFile)\n\t}\n\tif cfg.OutputDir != \"\" {\n\t\targs = append(args, \"--output-dir\", cfg.OutputDir)\n\t}\n\n\tif err := util.RunCmd(\"kubebuilder edit\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run edit subcommand for Helm plugin: %w\", err)\n\t}\n\treturn nil\n}\n\n// Edits the project to include the Helm plugin.\nfunc kubebuilderHelmEdit(isV2Alpha bool) error {\n\tvar pluginKey string\n\tif isV2Alpha {\n\t\tpluginKey = plugin.KeyFor(helmv2alpha.Plugin{})\n\t} else {\n\t\tpluginKey = plugin.KeyFor(helmv1alpha.Plugin{})\n\t}\n\n\targs := []string{\"edit\", \"--plugins\", pluginKey}\n\tif err := util.RunCmd(\"kubebuilder edit\", \"kubebuilder\", args...); err != nil {\n\t\treturn fmt.Errorf(\"failed to run edit subcommand for Helm plugin: %w\", err)\n\t}\n\treturn nil\n}\n\n// hasHelmPlugin checks if any Helm plugin (v1alpha or v2alpha) is present by inspecting\n// the plugin chain or configuration.\nfunc hasHelmPlugin(cfg store.Store) (bool, bool) {\n\tvar pluginConfig map[string]any\n\n\t// Check for v2alpha first (preferred)\n\terr := cfg.Config().DecodePluginConfig(plugin.KeyFor(helmv2alpha.Plugin{}), &pluginConfig)\n\tif err == nil {\n\t\treturn true, true // has helm plugin, is v2alpha\n\t}\n\n\t// Check for v1alpha\n\terr = cfg.Config().DecodePluginConfig(plugin.KeyFor(helmv1alpha.Plugin{}), &pluginConfig)\n\tif err != nil {\n\t\t// If neither Helm plugin is found, return false\n\t\tif errors.As(err, &config.PluginKeyNotFoundError{}) {\n\t\t\treturn false, false\n\t\t}\n\t\t// slog other errors if needed\n\t\tslog.Error(\"error decoding Helm plugin config\", \"error\", err)\n\t\treturn false, false\n\t}\n\n\t// v1alpha Helm plugin is present\n\treturn true, false // has helm plugin, is not v2alpha\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/generate_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage internal\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tdeployimagev1alpha1 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\"\n\tautoupdatev1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\ntype fakeConfig struct {\n\tconfig.Config\n\tpluginChain []string\n\tdomain      string\n\trepo        string\n\tprojectName string\n\tmultigroup  bool\n\tnamespaced  bool\n\tresources   []resource.Resource\n\tpluginErr   error\n\tgetResErr   error\n\tplugins     map[string]any\n}\n\nfunc (f *fakeConfig) GetPluginChain() []string { return f.pluginChain }\nfunc (f *fakeConfig) GetDomain() string        { return f.domain }\nfunc (f *fakeConfig) GetRepository() string    { return f.repo }\nfunc (f *fakeConfig) GetProjectName() string   { return f.projectName }\nfunc (f *fakeConfig) IsMultiGroup() bool       { return f.multigroup }\nfunc (f *fakeConfig) IsNamespaced() bool       { return f.namespaced }\nfunc (f *fakeConfig) GetResources() ([]resource.Resource, error) {\n\tif f.getResErr != nil {\n\t\treturn nil, f.getResErr\n\t}\n\treturn f.resources, nil\n}\n\nfunc (f *fakeConfig) DecodePluginConfig(key string, dst any) error {\n\tif len(f.plugins) == 0 {\n\t\treturn config.PluginKeyNotFoundError{Key: key}\n\t}\n\tif f.pluginErr != nil {\n\t\treturn f.pluginErr\n\t}\n\t// Check if the specific key exists\n\tval, exists := f.plugins[key]\n\tif !exists {\n\t\treturn config.PluginKeyNotFoundError{Key: key}\n\t}\n\t// If the value is a struct, copy its fields to dst\n\tif val != nil && dst != nil {\n\t\t// Handle different plugin config types\n\t\tswitch d := dst.(type) {\n\t\tcase *autoupdatev1alpha.PluginConfig:\n\t\t\tif v, ok := val.(autoupdatev1alpha.PluginConfig); ok {\n\t\t\t\t*d = v\n\t\t\t}\n\t\tcase *deployimagev1alpha1.PluginConfig:\n\t\t\tif v, ok := val.(deployimagev1alpha1.PluginConfig); ok {\n\t\t\t\t*d = v\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\ntype fakeStore struct {\n\tstore.Store\n\tcfg *fakeConfig\n}\n\nfunc (f *fakeStore) Config() config.Config { return f.cfg }\n\nfunc TestGenerateHelpers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Generate helpers Suite\")\n}\n\n// setupKubebuilderMockEnvironment sets up a mock for kubebuilder testing\nfunc setupKubebuilderMockEnvironment(kbc *utils.TestContext) string {\n\t// Save current working directory for restoration\n\toriginalDir, err := os.Getwd()\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// Create a PROJECT file in the test directory with proper YAML format\n\tprojectFilePath := filepath.Join(kbc.Dir, \"PROJECT\")\n\tprojectFileContent := []byte(`# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ndomain: example.com\nlayout:\n- go.kubebuilder.io/v4\nprojectName: test-project\nrepo: github.com/example/test-project\nversion: \"3\"\n`)\n\tExpect(os.WriteFile(projectFilePath, projectFileContent, 0o644)).To(Succeed())\n\n\t// Create mock kubebuilder binary\n\tmockKubebuilderPath := filepath.Join(kbc.Dir, \"kubebuilder\")\n\tmockScript := `#!/bin/bash\n# Log all commands to a file for debugging\nlog_file=\"` + filepath.Join(kbc.Dir, \"kubebuilder.log\") + `\"\necho \"$@\" >> \"$log_file\"\n\nif [[ \"$1\" == \"init\" ]]; then\n\techo \"kubebuilder init mock executed\"\n\texit 0\nelif [[ \"$1\" == \"create\" && \"$2\" == \"api\" ]]; then\n\techo \"kubebuilder create api mock executed\"\n\texit 0\nelif [[ \"$1\" == \"create\" && \"$2\" == \"webhook\" ]]; then\n\techo \"kubebuilder create webhook mock executed\"\n\texit 0\nelif [[ \"$1\" == \"edit\" ]]; then\n\techo \"kubebuilder edit mock executed\"\n\texit 0\nelse\n\techo \"kubebuilder mock executed with args: $@\"\n\texit 0\nfi`\n\tExpect(os.WriteFile(mockKubebuilderPath, []byte(mockScript), 0o755)).To(Succeed())\n\n\t// Create mock make binary\n\tmockMakePath := filepath.Join(kbc.Dir, \"make\")\n\tmakeScript := `#!/bin/bash\n\t# Log all commands to a file for debugging\n\tlog_file=\"` + filepath.Join(kbc.Dir, \"make.log\") + `\"\n\techo \"$@\" >> \"$log_file\"\n\techo \"make mock executed with target: $@\"\n\texit 0`\n\tExpect(os.WriteFile(mockMakePath, []byte(makeScript), 0o755)).To(Succeed())\n\n\t// Add the test directory to PATH so the mock binaries are found\n\toldPath := os.Getenv(\"PATH\")\n\tnewPath := fmt.Sprintf(\"%s:%s\", kbc.Dir, oldPath)\n\tExpect(os.Setenv(\"PATH\", newPath)).To(Succeed())\n\n\t// Change to the test directory so kubebuilder can find the PROJECT file\n\tExpect(os.Chdir(kbc.Dir)).To(Succeed())\n\n\t// Return the original directory for restoration\n\treturn originalDir\n}\n\nvar _ = Describe(\"generate: validate\", func() {\n\tvar (\n\t\tkbc *utils.TestContext\n\t\terr error\n\t)\n\n\tBeforeEach(func() {\n\t\t// Initialize TestContext\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t// Create a PROJECT file in the test directory\n\t\tprojectFilePath := filepath.Join(kbc.Dir, \"PROJECT\")\n\t\tprojectFileContent := []byte(\"domain: example.com\\nrepo: github.com/example/repo\\n\")\n\t\tExpect(os.WriteFile(projectFilePath, projectFileContent, 0o644)).To(Succeed())\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"cleaning up test artifacts\")\n\t\tkbc.Destroy()\n\t})\n\n\t// Validate\n\tContext(\"Validate\", func() {\n\t\tContext(\"Success\", func() {\n\t\t\tIt(\"succeeds\", func() {\n\t\t\t\tg := &Generate{InputDir: kbc.Dir}\n\t\t\t\tExpect(g.Validate()).To(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Failure\", func() {\n\t\t\tIt(\"returns error if GetInputPath fails\", func() {\n\t\t\t\tg := &Generate{InputDir: filepath.Join(kbc.Dir, \"notfound\")}\n\t\t\t\tExpect(g.Validate()).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: directory-helpers\", func() {\n\tvar (\n\t\ttmpDir string\n\t\terr    error\n\t)\n\tBeforeEach(func() {\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"testdir\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\tAfterEach(func() {\n\t\tExpect(os.RemoveAll(tmpDir)).To(Succeed())\n\t})\n\n\t// createDirectory\n\tContext(\"createDirectory\", func() {\n\t\tIt(\"creates directory successfully\", func() {\n\t\t\tdir := filepath.Join(tmpDir, \"testdir-generate-go\")\n\t\t\tExpect(createDirectory(dir)).To(Succeed())\n\t\t\t_, err = os.Stat(dir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\t\tIt(\"returns error for invalid path\", func() {\n\t\t\tExpect(createDirectory(\"/dev/null/foo\")).NotTo(Succeed())\n\t\t})\n\t})\n\n\t// changeWorkingDirectory\n\tContext(\"changeWorkingDirectory\", func() {\n\t\tvar originalDir string\n\n\t\tBeforeEach(func() {\n\t\t\t// Save current working directory\n\t\t\toriginalDir, err = os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// Restore original working directory\n\t\t\tExpect(os.Chdir(originalDir)).To(Succeed())\n\t\t})\n\n\t\tIt(\"changes working directory successfully\", func() {\n\t\t\t// Create a test file in the target directory to verify we can access it after changing\n\t\t\ttestFile := filepath.Join(tmpDir, \"test-file.txt\")\n\t\t\ttestContent := \"test content\"\n\t\t\tExpect(os.WriteFile(testFile, []byte(testContent), 0o644)).To(Succeed())\n\n\t\t\t// Change to the directory\n\t\t\tExpect(changeWorkingDirectory(tmpDir)).To(Succeed())\n\n\t\t\t// Verify we're in the correct directory by checking we can read the file\n\t\t\t// using relative path (this proves we're in the right directory)\n\t\t\tcontent, err := os.ReadFile(\"test-file.txt\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).To(Equal(testContent))\n\n\t\t\t// Also verify we can create a new file in the current directory\n\t\t\tnewFile := \"new-test-file.txt\"\n\t\t\tnewContent := \"new content\"\n\t\t\tExpect(os.WriteFile(newFile, []byte(newContent), 0o644)).To(Succeed())\n\n\t\t\t// Verify the file was created in the expected location\n\t\t\tfullPath := filepath.Join(tmpDir, newFile)\n\t\t\tverifyContent, err := os.ReadFile(fullPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(verifyContent)).To(Equal(newContent))\n\t\t})\n\n\t\tIt(\"returns error for non-existent directory\", func() {\n\t\t\tnonExistentDir := filepath.Join(tmpDir, \"nonexistent\")\n\t\t\tExpect(changeWorkingDirectory(nonExistentDir)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"returns error for invalid path\", func() {\n\t\t\tExpect(changeWorkingDirectory(\"/dev/null/foo\")).NotTo(Succeed())\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: file-helpers\", func() {\n\tvar (\n\t\ttmpDir string\n\t\terr    error\n\t)\n\tBeforeEach(func() {\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"testdir\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\tAfterEach(func() {\n\t\tExpect(os.RemoveAll(tmpDir)).To(Succeed())\n\t})\n\n\t// copyFile\n\tContext(\"copyFile\", func() {\n\t\tContext(\"success\", func() {\n\t\t\tvar src, dst string\n\t\t\tBeforeEach(func() {\n\t\t\t\tsrc = filepath.Join(tmpDir, \"src.txt\")\n\t\t\t\tdst = filepath.Join(tmpDir, \"dst.txt\")\n\t\t\t\tExpect(os.WriteFile(src, []byte(\"hello\"), 0o644)).To(Succeed())\n\t\t\t})\n\t\t\tAfterEach(func() {\n\t\t\t\tExpect(os.Remove(src)).To(Succeed())\n\t\t\t\tExpect(os.Remove(dst)).To(Succeed())\n\t\t\t})\n\n\t\t\tIt(\"copies file successfully\", func() {\n\t\t\t\tExpect(copyFile(src, dst)).To(Succeed())\n\t\t\t\tb, err := os.ReadFile(dst)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(\"hello\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"failure\", func() {\n\t\t\tIt(\"returns error if src does not exist\", func() {\n\t\t\t\tsrc := filepath.Join(tmpDir, \"notfound\")\n\t\t\t\tdst := filepath.Join(tmpDir, \"nowhere\")\n\t\t\t\tExpect(copyFile(src, dst)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: get-args-helpers\", func() {\n\t// getInitArgs\n\tDescribe(\"getInitArgs\", func() {\n\t\tContext(\"for outdated plugins\", func() {\n\t\t\tWhen(\"v3 plugin is used\", func() {\n\t\t\t\tIt(\"should return correct args for plugins, domain, repo\", func() {\n\t\t\t\t\tcfg := &fakeConfig{pluginChain: []string{\"go.kubebuilder.io/v3\"}, domain: \"foo.com\", repo: \"bar\"}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"alpha plugin is used\", func() {\n\t\t\t\tIt(\"should return correct args for plugins, domain, repo\", func() {\n\t\t\t\t\tcfg := &fakeConfig{pluginChain: []string{\"go.kubebuilder.io/v3-alpha\"}, domain: \"foo.com\", repo: \"bar\"}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"helm v1-alpha plugin is used\", func() {\n\t\t\t\tIt(\"should replace with helm v2-alpha\", func() {\n\t\t\t\t\tcfg := &fakeConfig{\n\t\t\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"helm.kubebuilder.io/v1-alpha\"},\n\t\t\t\t\t\tdomain:      \"foo.com\",\n\t\t\t\t\t\trepo:        \"bar\",\n\t\t\t\t\t}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"helm.kubebuilder.io/v2-alpha\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\"))\n\t\t\t\t\tExpect(args).NotTo(ContainElement(ContainSubstring(\"helm.kubebuilder.io/v1-alpha\")))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"for latest plugins\", func() {\n\t\t\tWhen(\"latest plugin (v4) is used\", func() {\n\t\t\t\tIt(\"returns correct args for plugins, domain, repo\", func() {\n\t\t\t\t\tcfg := &fakeConfig{pluginChain: []string{\"go.kubebuilder.io/v4\"}, domain: \"foo.com\", repo: \"bar\"}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"project name is set\", func() {\n\t\t\t\tIt(\"returns correct args including project name\", func() {\n\t\t\t\t\tcfg := &fakeConfig{\n\t\t\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t\t\t\tdomain:      \"foo.com\",\n\t\t\t\t\t\trepo:        \"bar\",\n\t\t\t\t\t\tprojectName: \"my-project\",\n\t\t\t\t\t}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\", \"--project-name\", \"my-project\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"multigroup flag is enabled\", func() {\n\t\t\t\tIt(\"includes --multigroup in init args\", func() {\n\t\t\t\t\tcfg := &fakeConfig{\n\t\t\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t\t\t\tdomain:      \"foo.com\",\n\t\t\t\t\t\trepo:        \"bar\",\n\t\t\t\t\t\tmultigroup:  true,\n\t\t\t\t\t}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\", \"--multigroup\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"namespaced flag is enabled\", func() {\n\t\t\t\tIt(\"includes --namespaced in init args\", func() {\n\t\t\t\t\tcfg := &fakeConfig{\n\t\t\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t\t\t\tdomain:      \"foo.com\",\n\t\t\t\t\t\trepo:        \"bar\",\n\t\t\t\t\t\tnamespaced:  true,\n\t\t\t\t\t}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\", \"--namespaced\"))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"both multigroup and namespaced are enabled\", func() {\n\t\t\t\tIt(\"includes both flags in init args\", func() {\n\t\t\t\t\tcfg := &fakeConfig{\n\t\t\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t\t\t\tdomain:      \"foo.com\",\n\t\t\t\t\t\trepo:        \"bar\",\n\t\t\t\t\t\tmultigroup:  true,\n\t\t\t\t\t\tnamespaced:  true,\n\t\t\t\t\t}\n\t\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\t\targs := getInitArgs(store)\n\t\t\t\t\tExpect(args).To(ContainElements(\"--plugins\", ContainSubstring(\"go.kubebuilder.io/v4\"),\n\t\t\t\t\t\t\"--domain\", \"foo.com\", \"--repo\", \"bar\", \"--multigroup\", \"--namespaced\"))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\t// getGVKFlags\n\tContext(\"getGVKFlags\", func() {\n\t\tIt(\"returns correct flags\", func() {\n\t\t\tres := resource.Resource{Plural: \"foos\"}\n\t\t\tres.Group = \"example.com\"\n\t\t\tres.Version = \"v1\"\n\t\t\tres.Kind = \"Foo\"\n\t\t\tflags := getGVKFlags(res)\n\t\t\tExpect(flags).To(ContainElements(\"--plural\", \"foos\", \"--group\", \"example.com\", \"--version\", \"v1\", \"--kind\", \"Foo\"))\n\t\t})\n\t})\n\n\t// getGVKFlagsFromDeployImage\n\tContext(\"getGVKFlagsFromDeployImage\", func() {\n\t\tIt(\"returns correct flags\", func() {\n\t\t\trd := deployimagev1alpha1.ResourceData{Group: \"example.com\", Version: \"v1\", Kind: \"Foo\"}\n\t\t\tflags := getGVKFlagsFromDeployImage(rd)\n\t\t\tExpect(flags).To(ContainElements(\"--group\", \"example.com\", \"--version\", \"v1\", \"--kind\", \"Foo\"))\n\t\t})\n\t})\n\n\t// getDeployImageOptions\n\tContext(\"getDeployImageOptions\", func() {\n\t\tIt(\"returns correct options\", func() {\n\t\t\trd := deployimagev1alpha1.ResourceData{}\n\t\t\trd.Options.Image = \"test-kubebuilder\"\n\t\t\trd.Options.ContainerCommand = \"echo 'Hello'\"\n\t\t\trd.Options.ContainerPort = \"8000\"\n\t\t\trd.Options.RunAsUser = \"test\"\n\t\t\topts := getDeployImageOptions(rd)\n\t\t\tExpect(opts).To(ContainElements(\"--image=test-kubebuilder\",\n\t\t\t\t\"--image-container-command=echo 'Hello'\",\n\t\t\t\t\"--image-container-port=8000\",\n\t\t\t\t\"--run-as-user=test\",\n\t\t\t\t\"--plugins=deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t\t})\n\t})\n\n\t// getAPIResourceFlags\n\tContext(\"getAPIResourceFlags\", func() {\n\t\tvar res resource.Resource\n\t\tBeforeEach(func() {\n\t\t\tres = resource.Resource{API: &resource.API{}}\n\t\t})\n\n\t\tContext(\"returns correct flags\", func() {\n\t\t\tIt(\"for nil API with Controller set\", func() {\n\t\t\t\tres.Controller = true\n\t\t\t\tExpect(getAPIResourceFlags(res)).To(ContainElements(\"--resource=false\", \"--controller\"))\n\t\t\t})\n\t\t\tIt(\"for non nil API (namespaced not set) with Controller not set\", func() {\n\t\t\t\tres.API.CRDVersion = \"v1\"\n\t\t\t\tres.API.Namespaced = true\n\t\t\t\tExpect(getAPIResourceFlags(res)).To(ContainElements(\"--resource\", \"--namespaced\", \"--controller=false\"))\n\t\t\t})\n\t\t\tIt(\"for non nil API (namespaced set) with Controller not set\", func() {\n\t\t\t\tres.API.CRDVersion = \"v1\"\n\t\t\t\tres.API.Namespaced = false\n\t\t\t\tExpect(getAPIResourceFlags(res)).To(ContainElements(\"--resource\", \"--namespaced=false\", \"--controller=false\"))\n\t\t\t})\n\t\t})\n\t})\n\t// getWebhookResourceFlags\n\tContext(\"getWebhookResourceFlags\", func() {\n\t\tIt(\"returns correct flags for specified resources\", func() {\n\t\t\tres := resource.Resource{\n\t\t\t\tPath:     \"external/test\",\n\t\t\t\tGVK:      resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Example\", Domain: \"test\"},\n\t\t\t\tExternal: true,\n\t\t\t\tWebhooks: &resource.Webhooks{\n\t\t\t\t\tValidation: true,\n\t\t\t\t\tDefaulting: true,\n\t\t\t\t\tConversion: true,\n\t\t\t\t\tSpoke:      []string{\"v2\"},\n\t\t\t\t},\n\t\t\t}\n\t\t\tflags := getWebhookResourceFlags(res)\n\t\t\tExpect(flags).To(ContainElements(\"--external-api-path\", \"external/test\", \"--external-api-domain\", \"test\",\n\t\t\t\t\"--programmatic-validation\", \"--defaulting\", \"--conversion\", \"--spoke\", \"v2\"))\n\t\t})\n\n\t\tIt(\"returns correct flags for external resources with module version\", func() {\n\t\t\tres := resource.Resource{\n\t\t\t\tPath:     \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\",\n\t\t\t\tModule:   \"github.com/cert-manager/cert-manager@v1.18.2\",\n\t\t\t\tGVK:      resource.GVK{Group: \"cert-manager\", Version: \"v1\", Kind: \"Certificate\", Domain: \"io\"},\n\t\t\t\tExternal: true,\n\t\t\t\tWebhooks: &resource.Webhooks{\n\t\t\t\t\tDefaulting: true,\n\t\t\t\t},\n\t\t\t}\n\t\t\tflags := getWebhookResourceFlags(res)\n\t\t\tExpect(flags).To(ContainElement(\"--external-api-path\"))\n\t\t\tExpect(flags).To(ContainElement(\"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"))\n\t\t\tExpect(flags).To(ContainElement(\"--external-api-domain\"))\n\t\t\tExpect(flags).To(ContainElement(\"io\"))\n\t\t\tExpect(flags).To(ContainElement(\"--external-api-module\"))\n\t\t\tExpect(flags).To(ContainElement(\"github.com/cert-manager/cert-manager@v1.18.2\"))\n\t\t\tExpect(flags).To(ContainElement(\"--defaulting\"))\n\t\t})\n\n\t\tIt(\"returns correct flags for external resources WITHOUT module version\", func() {\n\t\t\tres := resource.Resource{\n\t\t\t\tPath:     \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\",\n\t\t\t\tModule:   \"\", // No module specified\n\t\t\t\tGVK:      resource.GVK{Group: \"cert-manager\", Version: \"v1\", Kind: \"Certificate\", Domain: \"io\"},\n\t\t\t\tExternal: true,\n\t\t\t\tWebhooks: &resource.Webhooks{\n\t\t\t\t\tDefaulting: true,\n\t\t\t\t},\n\t\t\t}\n\t\t\tflags := getWebhookResourceFlags(res)\n\t\t\tExpect(flags).To(ContainElement(\"--external-api-path\"))\n\t\t\tExpect(flags).To(ContainElement(\"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"))\n\t\t\tExpect(flags).To(ContainElement(\"--external-api-domain\"))\n\t\t\tExpect(flags).To(ContainElement(\"io\"))\n\t\t\tExpect(flags).NotTo(ContainElement(\"--external-api-module\"))\n\t\t\tExpect(flags).To(ContainElement(\"--defaulting\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: create-helpers\", func() {\n\tvar (\n\t\tkbc         *utils.TestContext\n\t\terr         error\n\t\toriginalDir string\n\t)\n\n\tBeforeEach(func() {\n\t\t// Initialize TestContext\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t// Setup mock kubebuilder environment\n\t\toriginalDir = setupKubebuilderMockEnvironment(kbc)\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"cleaning up test artifacts\")\n\t\t// Restore original working directory\n\t\tExpect(os.Chdir(originalDir)).To(Succeed())\n\t\tkbc.Destroy()\n\t})\n\n\t// createAPI\n\tDescribe(\"createAPI\", func() {\n\t\tContext(\"Without External flag\", func() {\n\t\t\tIt(\"runs kubebuilder create api successfully for a resource\", func() {\n\t\t\t\tres := resource.Resource{\n\t\t\t\t\tGVK:        resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Example\", Domain: \"test\"},\n\t\t\t\t\tPlural:     \"examples\",\n\t\t\t\t\tAPI:        &resource.API{Namespaced: true},\n\t\t\t\t\tController: true,\n\t\t\t\t}\n\t\t\t\t// Run createAPI and verify no errors\n\t\t\t\tExpect(createAPI(res)).To(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With External flag set\", func() {\n\t\t\tIt(\"runs kubebuilder create api successfully for a resource\", func() {\n\t\t\t\tres := resource.Resource{\n\t\t\t\t\tGVK:        resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Example\", Domain: \"external\"},\n\t\t\t\t\tPlural:     \"examples\",\n\t\t\t\t\tAPI:        &resource.API{Namespaced: true},\n\t\t\t\t\tController: true,\n\t\t\t\t\tExternal:   true,\n\t\t\t\t\tPath:       \"external/path\",\n\t\t\t\t}\n\t\t\t\t// Run createAPI and verify no errors\n\t\t\t\tExpect(createAPI(res)).To(Succeed())\n\t\t\t})\n\n\t\t\tIt(\"runs kubebuilder create api successfully with module version\", func() {\n\t\t\t\tres := resource.Resource{\n\t\t\t\t\tGVK:        resource.GVK{Group: \"cert-manager\", Version: \"v1\", Kind: \"Certificate\", Domain: \"io\"},\n\t\t\t\t\tPlural:     \"certificates\",\n\t\t\t\t\tAPI:        nil, // External resources typically don't scaffold API\n\t\t\t\t\tController: true,\n\t\t\t\t\tExternal:   true,\n\t\t\t\t\tPath:       \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\",\n\t\t\t\t\tModule:     \"github.com/cert-manager/cert-manager@v1.18.2\",\n\t\t\t\t}\n\t\t\t\t// Run createAPI and verify no errors\n\t\t\t\tExpect(createAPI(res)).To(Succeed())\n\t\t\t})\n\n\t\t\tIt(\"runs kubebuilder create api successfully WITHOUT module version\", func() {\n\t\t\t\tres := resource.Resource{\n\t\t\t\t\tGVK:        resource.GVK{Group: \"cert-manager\", Version: \"v1\", Kind: \"Certificate\", Domain: \"io\"},\n\t\t\t\t\tPlural:     \"certificates\",\n\t\t\t\t\tAPI:        nil,\n\t\t\t\t\tController: true,\n\t\t\t\t\tExternal:   true,\n\t\t\t\t\tPath:       \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\",\n\t\t\t\t\tModule:     \"\", // No module specified\n\t\t\t\t}\n\t\t\t\t// Run createAPI and verify no errors\n\t\t\t\tExpect(createAPI(res)).To(Succeed())\n\t\t\t})\n\t\t})\n\t})\n\n\t// createWebhook\n\tDescribe(\"createWebhook\", func() {\n\t\tIt(\"runs kubebuilder create webhook successfully for a resource\", func() {\n\t\t\tres := resource.Resource{\n\t\t\t\tGVK:      resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Example\", Domain: \"test\"},\n\t\t\t\tPlural:   \"examples\",\n\t\t\t\tWebhooks: &resource.Webhooks{WebhookVersion: \"v1\"},\n\t\t\t}\n\t\t\t// Run createWebhook and verify no errors\n\t\t\tExpect(createWebhook(res)).To(Succeed())\n\t\t})\n\n\t\tIt(\"ignores web creation if webhook resource is empty\", func() {\n\t\t\tres := resource.Resource{\n\t\t\t\tGVK:      resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Example\", Domain: \"test\"},\n\t\t\t\tPlural:   \"examples\",\n\t\t\t\tWebhooks: &resource.Webhooks{},\n\t\t\t}\n\t\t\t// Run createWebhook and verify no errors\n\t\t\tExpect(createWebhook(res)).To(Succeed())\n\t\t})\n\t})\n\n\tDescribe(\"createAPIWithDeployImage\", func() {\n\t\tIt(\"runs kubebuilder create api successfully with deploy image\", func() {\n\t\t\tresourceData := deployimagev1alpha1.ResourceData{\n\t\t\t\tGroup:   \"example.com\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Example\",\n\t\t\t}\n\t\t\tresourceData.Options.Image = \"example-image\"\n\t\t\tresourceData.Options.ContainerCommand = \"run\"\n\t\t\tresourceData.Options.ContainerPort = \"8080\"\n\t\t\tresourceData.Options.Image = \"test\"\n\t\t\t// Run createAPIWithDeployImage and verify no errors\n\t\t\tExpect(createAPIWithDeployImage(resourceData)).To(Succeed())\n\t\t})\n\n\t\tIt(\"validates deploy-image works with external APIs without release version\", func() {\n\t\t\t// This test validates that deploy-image plugin can work with external APIs\n\t\t\t// even without pinned versions (backward compatibility)\n\t\t\tresourceData := deployimagev1alpha1.ResourceData{\n\t\t\t\tGroup:   \"cert-manager\",\n\t\t\t\tDomain:  \"io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Certificate\",\n\t\t\t}\n\t\t\tresourceData.Options.Image = \"busybox:1.36.1\"\n\t\t\tresourceData.Options.RunAsUser = \"1001\"\n\t\t\t// Run createAPIWithDeployImage and verify no errors\n\t\t\tExpect(createAPIWithDeployImage(resourceData)).To(Succeed())\n\t\t})\n\n\t\tIt(\"validates deploy-image can be used alongside external APIs with release version\", func() {\n\t\t\t// This test validates that when external APIs with release versions are used,\n\t\t\t// deploy-image plugin still works correctly\n\t\t\t// Note: The release field is stored in the Resource, not in DeployImage's ResourceData\n\t\t\tresourceData := deployimagev1alpha1.ResourceData{\n\t\t\t\tGroup:   \"example.com\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Memcached\",\n\t\t\t}\n\t\t\tresourceData.Options.Image = \"memcached:1.6.26\"\n\t\t\tresourceData.Options.ContainerPort = \"11211\"\n\t\t\t// Run createAPIWithDeployImage and verify no errors\n\t\t\tExpect(createAPIWithDeployImage(resourceData)).To(Succeed())\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: kubebuilder\", func() {\n\tvar (\n\t\tkbc                           *utils.TestContext\n\t\terr                           error\n\t\toriginalDir                   string\n\t\toriginalGetExecutablePathFunc func() (string, error)\n\t)\n\n\tBeforeEach(func() {\n\t\t// Save the original function\n\t\toriginalGetExecutablePathFunc = getExecutablePathFunc\n\n\t\t// Initialize TestContext\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t// Setup mock kubebuilder environment\n\t\toriginalDir = setupKubebuilderMockEnvironment(kbc)\n\n\t\t// Mock getExecutablePathFunc to return the mock kubebuilder binary path\n\t\tgetExecutablePathFunc = func() (string, error) {\n\t\t\treturn filepath.Join(kbc.Dir, \"kubebuilder\"), nil\n\t\t}\n\t})\n\n\tAfterEach(func() {\n\t\t// Restore the original getExecutablePath function\n\t\tgetExecutablePathFunc = originalGetExecutablePathFunc\n\n\t\t// Restore original working directory\n\t\tExpect(os.Chdir(originalDir)).To(Succeed())\n\n\t\t// Clean up test artifacts\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"kubebuilderInit\", func() {\n\t\tIt(\"runs kubebuilder init successfully\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t\tdomain:      \"example.com\",\n\t\t\t\trepo:        \"github.com/example/repo\",\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(kubebuilderInit(store)).To(Succeed())\n\t\t})\n\t})\n\n\tContext(\"kubebuilderCreate\", func() {\n\t\tIt(\"runs kubebuilder create successfully for resources\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tresources: []resource.Resource{\n\t\t\t\t\t{Plural: \"foos\", GVK: resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Foo\"}},\n\t\t\t\t\t{Plural: \"bars\", GVK: resource.GVK{Group: \"example.com\", Version: \"v1\", Kind: \"Bar\"}},\n\t\t\t\t},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t// Run kubebuilderCreate and verify no errors\n\t\t\tExpect(kubebuilderCreate(store)).To(Succeed())\n\t\t})\n\t})\n\n\tContext(\"kubebuilderGrafanaEdit\", func() {\n\t\tIt(\"runs kubebuilder edit successfully for Grafana plugin\", func() {\n\t\t\t// Run kubebuilderGrafanaEdit and verify no errors\n\t\t\tExpect(kubebuilderGrafanaEdit()).To(Succeed())\n\t\t})\n\t})\n\n\tContext(\"kubebuilderHelmEdit\", func() {\n\t\tIt(\"runs kubebuilder edit successfully for Helm plugin\", func() {\n\t\t\t// Run kubebuilderHelmEdit and verify no errors\n\t\t\tExpect(kubebuilderHelmEdit(true)).To(Succeed())\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"generate: hasHelmPlugin\", func() {\n\tIt(\"returns true if v2-alpha plugin present\", func() {\n\t\tcfg := &fakeConfig{plugins: map[string]any{\"helm.kubebuilder.io/v2-alpha\": true}}\n\t\tstore := &fakeStore{cfg: cfg}\n\t\thasPlugin, isV2Alpha := hasHelmPlugin(store)\n\t\tExpect(hasPlugin).To(BeTrue())\n\t\tExpect(isV2Alpha).To(BeTrue())\n\t})\n\n\tIt(\"returns true if v1-alpha plugin present\", func() {\n\t\tcfg := &fakeConfig{plugins: map[string]any{\"helm.kubebuilder.io/v1-alpha\": true}}\n\t\tstore := &fakeStore{cfg: cfg}\n\t\thasPlugin, isV2Alpha := hasHelmPlugin(store)\n\t\tExpect(hasPlugin).To(BeTrue())\n\t\tExpect(isV2Alpha).To(BeFalse())\n\t})\n\n\tIt(\"returns false if both plugins not found\", func() {\n\t\tcfg := &fakeConfig{pluginErr: &config.PluginKeyNotFoundError{Key: \"helm.kubebuilder.io/v2-beta\"}}\n\t\tstore := &fakeStore{cfg: cfg}\n\t\thasPlugin, isV2Alpha := hasHelmPlugin(store)\n\t\tExpect(hasPlugin).To(BeFalse())\n\t\tExpect(isV2Alpha).To(BeFalse())\n\t})\n})\n\nvar _ = Describe(\"generate: migrate-plugins\", func() {\n\tvar (\n\t\tkbc         *utils.TestContext\n\t\ttmpDir      string\n\t\terr         error\n\t\toriginalDir string\n\t)\n\n\tBeforeEach(func() {\n\t\t// Initialize TestContext\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t// Setup mock kubebuilder environment\n\t\toriginalDir = setupKubebuilderMockEnvironment(kbc)\n\n\t\ttmpDir = kbc.Dir\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"cleaning up test artifacts\")\n\t\t// Restore original working directory\n\t\tExpect(os.Chdir(originalDir)).To(Succeed())\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"migrateGrafanaPlugin\", func() {\n\t\tIt(\"skips migration as Grafana plugin not found\", func() {\n\t\t\tcfg := &fakeConfig{pluginErr: &config.PluginKeyNotFoundError{Key: \"grafana.kubebuilder.io/v1-alpha\"}}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateGrafanaPlugin(store, \"src\", \"dest\")).To(Succeed())\n\t\t})\n\n\t\tIt(\"returns error if decoding Grafana plugin config fails\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tpluginErr: fmt.Errorf(\"decoding error\"),\n\t\t\t\tplugins:   map[string]any{\"grafana.kubebuilder.io/v1-alpha\": true},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateGrafanaPlugin(store, \"src\", \"dest\")).NotTo(Succeed())\n\t\t})\n\n\t\tContext(\"success\", func() {\n\t\t\tvar src, dest string\n\t\t\tBeforeEach(func() {\n\t\t\t\tsrc = filepath.Join(tmpDir, \"src\")\n\t\t\t\tdest = filepath.Join(tmpDir, \"dest\")\n\t\t\t\tExpect(os.MkdirAll(filepath.Join(src, \"grafana/custom-metrics\"), 0o755)).To(Succeed())\n\t\t\t\tExpect(os.WriteFile(filepath.Join(src, \"grafana/custom-metrics/config.yaml\"),\n\t\t\t\t\t[]byte(\"config\"), 0o755)).To(Succeed())\n\t\t\t\tExpect(os.MkdirAll(filepath.Join(dest, \"grafana/custom-metrics\"), 0o755)).To(Succeed())\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tExpect(os.RemoveAll(src)).To(Succeed())\n\t\t\t\tExpect(os.RemoveAll(dest)).To(Succeed())\n\t\t\t})\n\n\t\t\tIt(\"migrates Grafana plugin successfully\", func() {\n\t\t\t\tcfg := &fakeConfig{plugins: map[string]any{\"grafana.kubebuilder.io/v1-alpha\": true}}\n\t\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\t\tExpect(migrateGrafanaPlugin(store, src, dest)).To(Succeed())\n\t\t\t\tb, err := os.ReadFile(filepath.Join(dest, \"grafana/custom-metrics/config.yaml\"))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(\"config\"))\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"migrateAutoUpdatePlugin\", func() {\n\t\tIt(\"skips migration as AutoUpdate plugin not found\", func() {\n\t\t\tcfg := &fakeConfig{pluginErr: &config.PluginKeyNotFoundError{Key: \"autoupdate.kubebuilder.io/v1-alpha\"}}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateAutoUpdatePlugin(store)).To(Succeed())\n\t\t})\n\n\t\tIt(\"returns error if failed to decode Auto Update plugin\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tpluginErr: fmt.Errorf(\"decoding error\"),\n\t\t\t\tplugins:   map[string]any{\"autoupdate.kubebuilder.io/v1-alpha\": true},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateAutoUpdatePlugin(store)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"migrates Auto Update plugin successfully without UseGHModels\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tplugins: map[string]any{\n\t\t\t\t\t\"autoupdate.kubebuilder.io/v1-alpha\": autoupdatev1alpha.PluginConfig{UseGHModels: false},\n\t\t\t\t},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateAutoUpdatePlugin(store)).To(Succeed())\n\t\t})\n\n\t\tIt(\"migrates Auto Update plugin successfully with UseGHModels enabled\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tplugins: map[string]any{\n\t\t\t\t\t\"autoupdate.kubebuilder.io/v1-alpha\": autoupdatev1alpha.PluginConfig{UseGHModels: true},\n\t\t\t\t},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateAutoUpdatePlugin(store)).To(Succeed())\n\t\t})\n\t})\n\n\tContext(\"migrateDeployImagePlugin\", func() {\n\t\tIt(\"returns error if failed to decode Deploy Image plugin\", func() {\n\t\t\tcfg := &fakeConfig{pluginErr: &config.PluginKeyNotFoundError{Key: \"deploy-image.kubebuilder.io/v1-alpha\"}}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateDeployImagePlugin(store)).To(Succeed())\n\t\t})\n\n\t\tIt(\"returns error if decoding Deploy Image plugin config fails\", func() {\n\t\t\tcfg := &fakeConfig{\n\t\t\t\tpluginErr: fmt.Errorf(\"decoding error\"),\n\t\t\t\tplugins:   map[string]any{\"deploy-image.kubebuilder.io/v1-alpha\": true},\n\t\t\t}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tExpect(migrateDeployImagePlugin(store)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"migrates Deploy Image plugin successfully\", func() {\n\t\t\tcfg := &fakeConfig{plugins: map[string]any{\"deploy-image.kubebuilder.io/v1-alpha\": true}}\n\t\t\tstore := &fakeStore{cfg: cfg}\n\n\t\t\t// Mock resources for the plugin\n\t\t\tresources := []deployimagev1alpha1.ResourceData{\n\t\t\t\t{\n\t\t\t\t\tGroup:   \"example.com\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Example\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tcfg.pluginChain = []string{\"deploy-image.kubebuilder.io/v1-alpha\"}\n\t\t\tstore.cfg = cfg\n\n\t\t\t// Use the mocked resources\n\t\t\tfor _, r := range resources {\n\t\t\t\tExpect(createAPIWithDeployImage(r)).To(Succeed())\n\t\t\t}\n\t\t\tExpect(migrateDeployImagePlugin(store)).To(Succeed())\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"Generate\", func() {\n\tvar (\n\t\tkbc                           *utils.TestContext\n\t\terr                           error\n\t\tg                             *Generate\n\t\toriginalDir                   string\n\t\toriginalGetExecutablePathFunc func() (string, error)\n\t)\n\n\tBeforeEach(func() {\n\t\t// Save the original function\n\t\toriginalGetExecutablePathFunc = getExecutablePathFunc\n\n\t\t// Initialize TestContext\n\t\tkbc, err = utils.NewTestContext(\"kubebuilder\", \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t// Setup mock kubebuilder environment\n\t\toriginalDir = setupKubebuilderMockEnvironment(kbc)\n\n\t\t// Mock getExecutablePathFunc to return the mock kubebuilder binary path\n\t\tgetExecutablePathFunc = func() (string, error) {\n\t\t\treturn filepath.Join(kbc.Dir, \"kubebuilder\"), nil\n\t\t}\n\n\t\t// Initialize Generate\n\t\tg = &Generate{InputDir: kbc.Dir}\n\t})\n\n\tAfterEach(func() {\n\t\t// Restore the original getExecutablePath function\n\t\tgetExecutablePathFunc = originalGetExecutablePathFunc\n\n\t\t// Restore original working directory\n\t\tExpect(os.Chdir(originalDir)).To(Succeed())\n\n\t\tBy(\"cleaning up test artifacts\")\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"outputDir is non empty\", func() {\n\t\tIt(\"scaffolds the project in output dir\", func() {\n\t\t\tg.OutputDir = kbc.Dir\n\t\t\tExpect(g.Generate()).To(Succeed())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/conflict.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io/fs\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n)\n\ntype ConflictSummary struct {\n\tMakefile bool // Makefile or makefile conflicted\n\tAPI      bool // anything under api/ or apis/ conflicted\n\tAnyGo    bool // any *.go file anywhere conflicted\n}\n\n// ConflictResult provides detailed conflict information for multiple use cases\ntype ConflictResult struct {\n\tSummary        ConflictSummary\n\tSourceFiles    []string // conflicted source files\n\tGeneratedFiles []string // conflicted generated files\n}\n\n// isGeneratedKB returns true for Kubebuilder-generated artifacts.\n// Moved from open_gh_issue.go to avoid duplication\nfunc isGeneratedKB(path string) bool {\n\treturn strings.Contains(path, \"/zz_generated.\") ||\n\t\tstrings.HasPrefix(path, \"config/crd/bases/\") ||\n\t\tstrings.HasPrefix(path, \"config/rbac/\") ||\n\t\tpath == \"dist/install.yaml\" ||\n\t\t// Generated deepcopy files\n\t\tstrings.HasSuffix(path, \"_deepcopy.go\")\n}\n\n// FindConflictFiles performs unified conflict detection for both conflict handling and GitHub issue generation\nfunc FindConflictFiles() ConflictResult {\n\tresult := ConflictResult{\n\t\tSourceFiles:    []string{},\n\t\tGeneratedFiles: []string{},\n\t}\n\n\t// Use git index for fast conflict detection first\n\tgitConflicts := getGitIndexConflicts()\n\n\t// Filesystem scan for conflict markers\n\tfsConflicts := scanFilesystemForConflicts()\n\n\t// Combine results and categorize\n\tallConflicts := make(map[string]bool)\n\tfor _, f := range gitConflicts {\n\t\tallConflicts[f] = true\n\t}\n\tfor _, f := range fsConflicts {\n\t\tallConflicts[f] = true\n\t}\n\n\t// Categorize into source vs generated\n\tfor file := range allConflicts {\n\t\tif isGeneratedKB(file) {\n\t\t\tresult.GeneratedFiles = append(result.GeneratedFiles, file)\n\t\t} else {\n\t\t\tresult.SourceFiles = append(result.SourceFiles, file)\n\t\t}\n\t}\n\n\tslices.Sort(result.SourceFiles)\n\tslices.Sort(result.GeneratedFiles)\n\n\t// Build summary for existing conflict.go usage\n\tresult.Summary = ConflictSummary{\n\t\tMakefile: hasConflictInFiles(allConflicts, \"Makefile\", \"makefile\"),\n\t\tAPI:      hasConflictInPaths(allConflicts, \"api\", \"apis\"),\n\t\tAnyGo:    hasGoConflictInFiles(allConflicts),\n\t}\n\n\treturn result\n}\n\n// DetectConflicts maintains backward compatibility\nfunc DetectConflicts() ConflictSummary {\n\treturn FindConflictFiles().Summary\n}\n\n// getGitIndexConflicts uses git ls-files to quickly find unmerged entries\nfunc getGitIndexConflicts() []string {\n\tout, err := exec.Command(\"git\", \"ls-files\", \"-u\").Output()\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tconflicts := make(map[string]bool)\n\tfor line := range strings.SplitSeq(string(out), \"\\n\") {\n\t\tfields := strings.Fields(line)\n\t\tif len(fields) >= 4 {\n\t\t\tfile := strings.Join(fields[3:], \" \")\n\t\t\tconflicts[file] = true\n\t\t}\n\t}\n\n\tresult := make([]string, 0, len(conflicts))\n\tfor file := range conflicts {\n\t\tresult = append(result, file)\n\t}\n\treturn result\n}\n\n// scanFilesystemForConflicts scans the working directory for conflict markers\nfunc scanFilesystemForConflicts() []string {\n\ttype void struct{}\n\tskipDir := map[string]void{\n\t\t\".git\":   {},\n\t\t\"vendor\": {},\n\t\t\"bin\":    {},\n\t}\n\n\tconst maxBytes = 2 << 20 // 2 MiB per file\n\n\tmarkersPrefix := [][]byte{\n\t\t[]byte(\"<<<<<<< \"),\n\t\t[]byte(\">>>>>>> \"),\n\t}\n\tmarkerExact := []byte(\"=======\")\n\n\tvar conflicts []string\n\n\t_ = filepath.WalkDir(\".\", func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn nil // best-effort\n\t\t}\n\t\t// Skip unwanted directories\n\t\tif d.IsDir() {\n\t\t\tif _, ok := skipDir[d.Name()]; ok {\n\t\t\t\treturn filepath.SkipDir\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// Quick size check\n\t\tfi, err := d.Info()\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tif fi.Size() > maxBytes {\n\t\t\treturn nil\n\t\t}\n\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tdefer func() {\n\t\t\tif cerr := f.Close(); cerr != nil {\n\t\t\t\tlog.Warn(\"failed to close file\", \"path\", path, \"error\", cerr)\n\t\t\t}\n\t\t}()\n\n\t\tfound := false\n\t\tsc := bufio.NewScanner(f)\n\t\t// allow long lines (YAML/JSON)\n\t\tbuf := make([]byte, 0, 1024*1024)\n\t\tsc.Buffer(buf, 4<<20)\n\n\t\tfor sc.Scan() {\n\t\t\tb := sc.Bytes()\n\t\t\t// starts with conflict markers\n\t\t\tfor _, p := range markersPrefix {\n\t\t\t\tif bytes.HasPrefix(b, p) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\t// exact middle marker line\n\t\t\tif !found && bytes.Equal(b, markerExact) {\n\t\t\t\tfound = true\n\t\t\t}\n\t\t\tif found {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif found {\n\t\t\tconflicts = append(conflicts, path)\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn conflicts\n}\n\n// Helper functions for backward compatibility\nfunc hasConflictInFiles(conflicts map[string]bool, paths ...string) bool {\n\tfor _, path := range paths {\n\t\tif conflicts[path] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc hasConflictInPaths(conflicts map[string]bool, pathPrefixes ...string) bool {\n\tfor file := range conflicts {\n\t\tfor _, prefix := range pathPrefixes {\n\t\t\tif strings.HasPrefix(file, prefix+\"/\") || file == prefix {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc hasGoConflictInFiles(conflicts map[string]bool) bool {\n\tfor file := range conflicts {\n\t\tif strings.HasSuffix(file, \".go\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// DecideMakeTargets applies simple policy over the summary.\nfunc DecideMakeTargets(cs ConflictSummary) []string {\n\tall := []string{\"manifests\", \"generate\", \"fmt\", \"vet\", \"lint-fix\"}\n\tif cs.Makefile {\n\t\treturn nil\n\t}\n\tkeep := make([]string, 0, len(all))\n\tfor _, t := range all {\n\t\tif cs.API && (t == \"manifests\" || t == \"generate\") {\n\t\t\tcontinue\n\t\t}\n\t\tif cs.AnyGo && (t == \"fmt\" || t == \"vet\" || t == \"lint-fix\") {\n\t\t\tcontinue\n\t\t}\n\t\tkeep = append(keep, t)\n\t}\n\treturn keep\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/conflict_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Conflict Detection\", func() {\n\tDescribe(\"isGeneratedKB\", func() {\n\t\tIt(\"should detect generated files\", func() {\n\t\t\tExpect(isGeneratedKB(\"api/v1/zz_generated.deepcopy.go\")).To(BeTrue())\n\t\t\tExpect(isGeneratedKB(\"config/crd/bases/crew.testproject.org_captains.yaml\")).To(BeTrue())\n\t\t\tExpect(isGeneratedKB(\"config/rbac/role.yaml\")).To(BeTrue())\n\t\t\tExpect(isGeneratedKB(\"dist/install.yaml\")).To(BeTrue())\n\t\t\tExpect(isGeneratedKB(\"api/v1/captain_deepcopy.go\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should not detect user files as generated\", func() {\n\t\t\tExpect(isGeneratedKB(\"api/v1/captain_types.go\")).To(BeFalse())\n\t\t\tExpect(isGeneratedKB(\"internal/controller/captain_controller.go\")).To(BeFalse())\n\t\t\tExpect(isGeneratedKB(\"internal/webhook/v1/captain_webhook.go\")).To(BeFalse())\n\t\t\tExpect(isGeneratedKB(\"internal/controller/suite_test.go\")).To(BeFalse())\n\t\t\tExpect(isGeneratedKB(\"cmd/main.go\")).To(BeFalse())\n\t\t\tExpect(isGeneratedKB(\"Makefile\")).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"FindConflictFiles\", func() {\n\t\tIt(\"should return a valid ConflictResult structure\", func() {\n\t\t\tresult := FindConflictFiles()\n\n\t\t\t// Should have the expected structure\n\t\t\tExpect(result.SourceFiles).NotTo(BeNil())\n\t\t\tExpect(result.GeneratedFiles).NotTo(BeNil())\n\t\t\tExpect(result.Summary).To(Equal(ConflictSummary{\n\t\t\t\tMakefile: result.Summary.Makefile,\n\t\t\t\tAPI:      result.Summary.API,\n\t\t\t\tAnyGo:    result.Summary.AnyGo,\n\t\t\t}))\n\t\t})\n\t})\n\n\tDescribe(\"DetectConflicts\", func() {\n\t\tIt(\"should maintain backward compatibility\", func() {\n\t\t\tsummary := DetectConflicts()\n\n\t\t\t// Should return a valid ConflictSummary\n\t\t\tExpect(summary.Makefile).To(BeFalse()) // No conflicts in test environment\n\t\t\tExpect(summary.API).To(BeFalse())\n\t\t\tExpect(summary.AnyGo).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"DecideMakeTargets\", func() {\n\t\tIt(\"should return all targets when no conflicts\", func() {\n\t\t\tcs := ConflictSummary{Makefile: false, API: false, AnyGo: false}\n\t\t\ttargets := DecideMakeTargets(cs)\n\n\t\t\texpected := []string{\"manifests\", \"generate\", \"fmt\", \"vet\", \"lint-fix\"}\n\t\t\tExpect(targets).To(Equal(expected))\n\t\t})\n\n\t\tIt(\"should return no targets when Makefile has conflicts\", func() {\n\t\t\tcs := ConflictSummary{Makefile: true, API: false, AnyGo: false}\n\t\t\ttargets := DecideMakeTargets(cs)\n\n\t\t\tExpect(targets).To(BeNil())\n\t\t})\n\n\t\tIt(\"should skip API targets when API has conflicts\", func() {\n\t\t\tcs := ConflictSummary{Makefile: false, API: true, AnyGo: false}\n\t\t\ttargets := DecideMakeTargets(cs)\n\n\t\t\texpected := []string{\"fmt\", \"vet\", \"lint-fix\"}\n\t\t\tExpect(targets).To(Equal(expected))\n\t\t})\n\n\t\tIt(\"should skip Go targets when Go files have conflicts\", func() {\n\t\t\tcs := ConflictSummary{Makefile: false, API: false, AnyGo: true}\n\t\t\ttargets := DecideMakeTargets(cs)\n\n\t\t\texpected := []string{\"manifests\", \"generate\"}\n\t\t\tExpect(targets).To(Equal(expected))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/download.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\tlog \"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/spf13/afero\"\n)\n\nconst KubebuilderReleaseURL = \"https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s\"\n\nfunc BuildReleaseURL(version string) string {\n\treturn fmt.Sprintf(KubebuilderReleaseURL, version, runtime.GOOS, runtime.GOARCH)\n}\n\n// DownloadReleaseVersionWith downloads the specified released version from GitHub releases and saves it\n// to a temporary directory with executable permissions.\n// Returns the temporary directory path containing the binary.\nfunc DownloadReleaseVersionWith(version string) (string, error) {\n\turl := BuildReleaseURL(version)\n\n\t// Create temp directory\n\tfs := afero.NewOsFs()\n\ttempDir, err := afero.TempDir(fs, \"\", \"kubebuilder\"+version+\"-\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temporary directory: %w\", err)\n\t}\n\n\t// Ensure cleanup on any error after this point\n\tcleanupOnErr := func() {\n\t\tif rmErr := os.RemoveAll(tempDir); rmErr != nil {\n\t\t\tlog.Error(\"failed to remove temporary directory\", \"dir\", tempDir, \"error\", rmErr)\n\t\t}\n\t}\n\n\tbinaryPath := filepath.Join(tempDir, \"kubebuilder\")\n\tf, err := fs.Create(binaryPath)\n\tif err != nil {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to create the binary file: %w\", err)\n\t}\n\tdefer func() {\n\t\tif closeErr := f.Close(); closeErr != nil {\n\t\t\tlog.Error(\"failed to close the binary file\", \"error\", closeErr)\n\t\t}\n\t}()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to build download request: %w\", err)\n\t}\n\treq.Header.Set(\"User-Agent\", \"kubebuilder-updater/1.0 (+https://github.com/kubernetes-sigs/kubebuilder)\")\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to download the binary: %w\", err)\n\t}\n\tdefer func() {\n\t\tif closeErr := resp.Body.Close(); closeErr != nil {\n\t\t\tlog.Error(\"failed to close HTTP response body\", \"error\", closeErr)\n\t\t}\n\t}()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to download the binary: HTTP %d\", resp.StatusCode)\n\t}\n\n\tif _, err := io.Copy(f, resp.Body); err != nil {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to write the binary content to file: %w\", err)\n\t}\n\n\t// Flush to disk before changing mode (best effort)\n\tif syncErr := f.Sync(); syncErr != nil {\n\t\tlog.Warn(\"failed to sync binary to disk (continuing)\", \"error\", syncErr)\n\t}\n\n\tif err := os.Chmod(binaryPath, 0o755); err != nil {\n\t\tcleanupOnErr()\n\t\treturn \"\", fmt.Errorf(\"failed to make binary executable: %w\", err)\n\t}\n\n\treturn tempDir, nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/download_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/h2non/gock\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"helpers\", func() {\n\tAfterEach(func() {\n\t\tgock.Off() // ensure HTTP mocks are cleared between tests\n\t})\n\n\tContext(\"BuildReleaseURL\", func() {\n\t\tIt(\"builds the exact URL for the current OS/ARCH\", func() {\n\t\t\tv := \"v4.5.0\"\n\t\t\texpected := fmt.Sprintf(KubebuilderReleaseURL, v, runtime.GOOS, runtime.GOARCH)\n\t\t\tExpect(BuildReleaseURL(v)).To(Equal(expected))\n\t\t})\n\t})\n\n\tContext(\"DownloadReleaseVersionWith\", func() {\n\t\tconst version = \"v4.6.0\"\n\n\t\tIt(\"downloads the binary and makes it executable\", func() {\n\t\t\t// Arrange: mock the GitHub release endpoint\n\t\t\turl := BuildReleaseURL(version)\n\t\t\tparts := strings.SplitN(url, \"/\", 4)\n\t\t\tExpect(parts).To(HaveLen(4))\n\t\t\thost := parts[0] + \"//\" + parts[2]\n\t\t\tpath := \"/\" + parts[3]\n\n\t\t\tgock.New(host).\n\t\t\t\tGet(path).\n\t\t\t\tReply(200).\n\t\t\t\tBodyString(\"#!/bin/sh\\necho kubebuilder\\n\")\n\n\t\t\tdir, err := DownloadReleaseVersionWith(version)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(dir).NotTo(BeEmpty())\n\n\t\t\tbin := filepath.Join(dir, \"kubebuilder\")\n\t\t\tst, statErr := os.Stat(bin)\n\t\t\tExpect(statErr).NotTo(HaveOccurred())\n\t\t\tExpect(st.Mode().IsRegular()).To(BeTrue())\n\n\t\t\tif runtime.GOOS != \"windows\" {\n\t\t\t\tExpect(st.Mode() & 0o111).NotTo(BeZero())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"returns a clear error when the server responds non-200\", func() {\n\t\t\turl := BuildReleaseURL(version)\n\t\t\tparts := strings.SplitN(url, \"/\", 4)\n\t\t\thost := parts[0] + \"//\" + parts[2]\n\t\t\tpath := \"/\" + parts[3]\n\n\t\t\tgock.New(host).\n\t\t\t\tGet(path).\n\t\t\t\tReply(401).\n\t\t\t\tBodyString(\"\")\n\n\t\t\t_, err := DownloadReleaseVersionWith(version)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to download the binary: HTTP 401\"))\n\t\t})\n\n\t\tIt(\"propagates network errors when the request fails\", func() {\n\t\t\turl := BuildReleaseURL(version)\n\t\t\tparts := strings.SplitN(url, \"/\", 4)\n\t\t\thost := parts[0] + \"//\" + parts[2]\n\t\t\tpath := \"/\" + parts[3]\n\n\t\t\tgock.New(host).\n\t\t\t\tGet(path).\n\t\t\t\tReplyError(errors.New(\"boom\"))\n\n\t\t\t_, err := DownloadReleaseVersionWith(version)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to download the binary:\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"boom\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/git_commands.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os/exec\"\n)\n\n// CommitIgnoreEmpty commits the staged changes with the provided message.\nfunc CommitIgnoreEmpty(msg, ctx string) error {\n\tcmd := exec.Command(\"git\", \"commit\", \"--no-verify\", \"-m\", msg)\n\tif err := cmd.Run(); err != nil {\n\t\tvar ee *exec.ExitError\n\t\tif errors.As(err, &ee) && ee.ExitCode() == 1 {\n\t\t\t// nothing to commit\n\t\t\tslog.Info(\"No changes to commit\", \"context\", ctx, \"message\", msg)\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"git commit failed (%s): %w\", ctx, err)\n\t}\n\treturn nil\n}\n\n// CleanWorktree removes everything in the repo root except .git so the next\n// checkout writes a verbatim snapshot of the source branch.\nfunc CleanWorktree(label string) error {\n\tif err := exec.Command(\"sh\", \"-c\",\n\t\t\"find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +\").Run(); err != nil {\n\t\treturn fmt.Errorf(\"cleanup for %s: %w\", label, err)\n\t}\n\treturn nil\n}\n\n// GitCmd creates a new git command with the provided git configuration\nfunc GitCmd(gitConfig []string, args ...string) *exec.Cmd {\n\tgitArgs := make([]string, 0, len(gitConfig)*2+len(args))\n\tfor _, kv := range gitConfig {\n\t\tgitArgs = append(gitArgs, \"-c\", kv)\n\t}\n\tgitArgs = append(gitArgs, args...)\n\treturn exec.Command(\"git\", gitArgs...)\n}\n\n// MergeCommitMessage returns the commit message for a successful merge update\nfunc MergeCommitMessage(from, to string) string {\n\treturn fmt.Sprintf(\"chore(kubebuilder): update scaffold %s -> %s\", from, to)\n}\n\n// ConflictCommitMessage returns the commit message for a merge update with conflicts\nfunc ConflictCommitMessage(from, to string) string {\n\t//nolint:lll\n\treturn fmt.Sprintf(\"chore(kubebuilder): (:warning: manual conflict resolution required) update scaffold %s -> %s\", from, to)\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/open_gh_issue.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n)\n\n// Curated-diff budgets (fixed; no env vars)\nconst (\n\tselectedDiffTotalCap     = 96 << 10 // 96 KiB total across all files\n\tselectedDiffLinesPerFile = 120      // default +/- lines per file\n\tselectedDiffLinesGoMod   = 240      // allow more for go.mod\n)\n\n// IssueTitleTmpl is the title template for the GitHub issue.\nconst IssueTitleTmpl = \"[Action Required] Upgrade the Scaffold: %[2]s -> %[1]s\"\n\n// IssueBodyTmpl is used when no conflicts are detected during the merge.\n//\n//nolint:lll\nconst IssueBodyTmpl = `## Description\n\nUpgrade your project to use the latest scaffold changes introduced in Kubebuilder [%[1]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[1]s).  \n\nSee the release notes from [%[3]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[3]s) to [%[1]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[1]s) for details about the changes included in this upgrade.\n\n## What to do\n\nA scheduled workflow already attempted this upgrade and created the branch %[4]s to help you in this process.\n\nCreate a Pull Request using the URL below to review the changes:\n%[2]s  \n\n## Next steps\n\n**Verify the changes**\n- Build the project  \n- Run tests  \n- Confirm everything still works\n\n:book: **More info:** https://kubebuilder.io/reference/commands/alpha_update\n`\n\n// IssueBodyTmplWithConflicts is used when conflicts are detected during the merge.\n//\n//nolint:lll\nconst IssueBodyTmplWithConflicts = `## Description\n\nUpgrade your project to use the latest scaffold changes introduced in Kubebuilder [%[1]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[1]s).  \n\nSee the release notes from [%[3]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[3]s) to [%[1]s](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%[1]s) for details about the changes included in this upgrade.\n\n## What to do\n\nA scheduled workflow already attempted this upgrade and created the branch (%[4]s) to help you in this process.\n\n:warning: **Conflicts were detected during the merge.**\n\nCreate a Pull Request using the URL below to review the changes and resolve conflicts manually:\n%[2]s  \n\n## Next steps\n\n### 1. Resolve conflicts\nAfter fixing conflicts, run:\n~~~bash\nmake manifests generate fmt vet lint-fix\n~~~\n\n### 2. Optional: work on a new branch\nTo apply the update in a clean branch, run:\n~~~bash\nkubebuilder alpha update --output-branch my-fix-branch\n~~~\n\nThis will create a new branch (my-fix-branch) with the update applied.  \nResolve conflicts there, complete the merge locally, and push the branch.\n\n### 3. Verify the changes\n- Build the project  \n- Run tests  \n- Confirm everything still works\n\n:book: **More info:** https://kubebuilder.io/reference/commands/alpha_update\n`\n\n// AiPRPrompt is the prompt to `gh models run`.\n//\n//nolint:lll\nconst AiPRPrompt = `You are a senior Go/K8s engineer. Produce a concise, reviewer-friendly **Pull Request summary** for a Kubebuilder project upgrade.\nStyle rules:\n- Use **simple, plain English** (like Kubebuilder docs).\n- Avoid jargon or long sentences.\n- Focus on clarity and readability for new contributors.\n\nRules (follow strictly):\n- Do NOT output angle-bracket placeholders like <OUTPUT_BRANCH>; use the real value from the context.\n- Do NOT guess versions. Only mention an exact version (e.g., controller-runtime v0.21.0)\n  if that exact version string appears in the provided diffs/context (e.g., go.mod).\n- When talking about dependencies:\n  - **Only** name modules that changed on **non-indirect** ` + \"`require`\" + ` lines in **go.mod** (i.e., lines **without** \"// indirect\").\n  - You may also name explicit tool versions found in **Makefile** or **Dockerfile** (e.g., controller-tools, golangci-lint, Go toolchain).\n  - **Never** name modules that appear only with \"// indirect\" or only in **go.sum** or generated files.\n  - If you cannot name any direct modules safely, write simply: \"dependencies updated\" (no module names).\n- Output exactly one overview and one reviewed-changes table. No duplicates.\n- Valid Markdown only. No \">>>\", no meta commentary.\n- Start with this exact sentence, substituting real values:\n  \"This is a Kubebuilder scaffold update from %s to %s on branch %s.\"\n  If a Compare PR URL is provided in the context header, append it **in parentheses** at the end of that sentence as a Markdown link, e.g., \" (see [compare PR](URL))\".\n- A \"conflict\" means the file currently contains Git merge markers (<<<<<<<, =======, >>>>>>>) and requires manual resolution. If no conflicts are provided in the context, omit the conflicts section entirely.\n- Conflicts section: ONLY add if there are conflicts. Do NOT invent conflicts.\n- Do NOT invent changes; use only what is in the context.\n\nRequired sections (Markdown, EXACT wording/case):\n\n## ( :robot: AI generate ) Scaffold Changes Overview\nStart with one short sentence: \"This is a Kubebuilder scaffold update from <FROM> to <TO> on branch <OUTPUT_BRANCH>.\" (with the optional compare link in parentheses at the end).\nThen list 4–6 concise bullet highlights (e.g., Go/tooling bumps, controller-runtime/k8s.io deps, security hardening like readOnlyRootFilesystem, error handling improvements).\nThen list **only the most important 6–10 bullet points** (never more than 10 items total in this section).\nIf there are many changes, summarize and cluster them (e.g., \"several small Go tooling bumps\") instead of listing everything.\n\n### ( :robot: AI generate ) Reviewed Changes\nAdd a collapsible block:\n<details>\n<summary>Show a summary per file</summary>\n\n| File | Description |\n| ---- | ----------- |\n| … | … |\n\n</details>\n\nBuild the table using ONLY the \"Changed files\" lists provided in the context. Do not invent files.\nIt is OK if some files also appear in the Conflicts section.\nIf there are many GENERATED files, you may **group them** using a glob with a count (e.g., ` + \"`config/crd/bases/*.yaml (12 files)`\" + `) instead of listing each one.\n\n**ONLY** if the context includes conflict files; add ANOTHER collapsible block titled **Conflicts Summary**:\n\n<details>\n<summary>Conflicts Summary</summary>\n\n| File | Description |\n| ---- | ----------- |\n| … | … |\n\n</details>\n\nA \"conflict\" means the file currently contains Git merge markers (<<<<<<<, =======, >>>>>>>) and requires manual resolution. If no conflicts are provided in the context, omit this section.\n\nList each conflicted file with a brief suggestion. For GENERATED files:\n- api/**/zz_generated.*.go: \"Do not edit by hand; run: make generate\"\n- config/crd/bases/*.yaml: \"Fix types in api/*_types.go; then run: make manifests\"\n- config/rbac/*.yaml: \"Fix markers in controllers/webhooks; then run: make manifests\"\n- dist/install.yaml: \"Fix conflicts; then run: make build-installer\"`\n\n// listConflictFiles uses the unified conflict detection from conflict.go\nfunc listConflictFiles() (src []string, gen []string) {\n\tconflicts := FindConflictFiles()\n\treturn conflicts.SourceFiles, conflicts.GeneratedFiles\n}\n\nfunc bulletList(items []string) string {\n\tif len(items) == 0 {\n\t\treturn \"<none>\"\n\t}\n\treturn \"- \" + strings.Join(items, \"\\n- \")\n}\n\n// FirstURL is a helper to grab the first URL-looking token from gh stdout\nfunc FirstURL(s string) string {\n\tfor f := range strings.FieldsSeq(s) {\n\t\tif strings.HasPrefix(f, \"http://\") || strings.HasPrefix(f, \"https://\") {\n\t\t\t// trim common trailing punctuation\n\t\t\treturn strings.TrimRight(f, \").,\")\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// IssueNumberFromURL returns the last path segment (…/issues/<n> ⇒ <n>).\nfunc IssueNumberFromURL(u string) string {\n\tu = strings.TrimSuffix(u, \"/\")\n\tif i := strings.LastIndex(u, \"/\"); i >= 0 && i+1 < len(u) {\n\t\treturn u[i+1:]\n\t}\n\treturn \"\"\n}\n\n// listChangedFiles returns files changed between base..head, split into SOURCE and GENERATED.\nfunc listChangedFiles(base, head string) (src []string, gen []string) {\n\tcmd := exec.Command(\"git\", \"diff\", \"--name-only\", \"-M\", \"--diff-filter=ACMRTD\", base+\"..\"+head)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, nil // best-effort\n\t}\n\tfor p := range strings.SplitSeq(strings.TrimSpace(string(out)), \"\\n\") {\n\t\tp = strings.TrimSpace(p)\n\t\tif p == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif isGeneratedKB(p) {\n\t\t\tgen = append(gen, p)\n\t\t} else {\n\t\t\tsrc = append(src, p)\n\t\t}\n\t}\n\tslices.Sort(src)\n\tslices.Sort(gen)\n\treturn src, gen\n}\n\n// BuildFullPrompet builds the AI context and writes it to a temp file.\n// It returns the absolute filepath to pass via --input-file/--file.\nfunc BuildFullPrompet(\n\tfromVersion, toVersion, baseBranch, outBranch, compareURL, releaseURL string,\n) string {\n\tchangedSrc, changedGen := listChangedFiles(baseBranch, outBranch)\n\tconflictSrc, conflictGen := listConflictFiles()\n\n\tvar ctx strings.Builder\n\n\tfmt.Fprintf(&ctx, \"Kubebuilder upgrade: %s -> %s\\n\", fromVersion, toVersion)\n\tfmt.Fprintf(&ctx, \"Compare PR URL: %s\\n\", compareURL)\n\tfmt.Fprintf(&ctx, \"Release notes: %s\\n\\n\", releaseURL)\n\tctx.WriteString(\"\\n\")\n\n\t// List changed files so the AI can build the Reviewed Changes table.\n\tif len(changedSrc) > 0 {\n\t\tfmt.Fprintf(&ctx, \"\\nChanged [SOURCE] files:\\n%s\\n\", bulletList(changedSrc))\n\t}\n\tif len(changedGen) > 0 {\n\t\tfmt.Fprintf(&ctx, \"\\nChanged [GENERATED] files:\\n%s\\n\", bulletList(changedGen))\n\t}\n\t// List conflicts for extra context (will be empty if none)\n\tif len(conflictSrc) > 0 {\n\t\tfmt.Fprintf(&ctx, \"\\nConflicted [SOURCE] files:\\n%s\\n\", bulletList(conflictSrc))\n\t}\n\tif len(conflictGen) > 0 {\n\t\tfmt.Fprintf(&ctx, \"\\nConflicted [GENERATED] files:\\n%s\\n\", bulletList(conflictGen))\n\t}\n\n\t// Concise, curated diffs for important SOURCE files only\n\tif len(changedSrc) > 0 {\n\t\tctx.WriteString(\"## Selected diffs\\n\")\n\t\t// Per-file cap is ignored for go.mod (it uses its own higher cap).\n\t\tconst perFileLineCap = selectedDiffLinesPerFile\n\t\t// total cap is fixed inside concatSelectedDiffs (selectedDiffTotalCap).\n\t\tctx.WriteString(concatSelectedDiffs(strings.TrimSpace(baseBranch),\n\t\t\tstrings.TrimSpace(outBranch), changedSrc, perFileLineCap, selectedDiffTotalCap))\n\t\tctx.WriteString(\"\\n\")\n\t}\n\n\treturn ctx.String()\n}\n\n// Never include these in curated diffs.\nfunc excludedFromDiff(p string) bool {\n\treturn isGeneratedKB(p) ||\n\t\tstrings.HasSuffix(p, \".md\") ||\n\t\tp == \"PROJECT\" ||\n\t\tp == \"go.sum\" ||\n\t\tstrings.HasPrefix(p, \"grafana/\") ||\n\t\tstrings.HasPrefix(p, \"config/crd/bases/\") ||\n\t\tstrings.HasPrefix(p, \"hack/\") ||\n\t\tstrings.HasPrefix(p, \"bin/\") ||\n\t\tstrings.HasPrefix(p, \"vendor/\") ||\n\t\tstrings.HasSuffix(p, \".log\")\n}\n\n// Only files that matter for KB review context (after exclusions).\nfunc importantFile(p string) bool {\n\tif excludedFromDiff(p) {\n\t\treturn false\n\t}\n\n\t// Critical Kubebuilder files\n\t//nolint:goconst\n\tif p == \"go.mod\" || p == \"Makefile\" || p == \"Dockerfile\" {\n\t\treturn true\n\t}\n\n\t// Core source code\n\tif strings.HasPrefix(p, \"cmd/\") ||\n\t\tstrings.HasPrefix(p, \"controllers/\") ||\n\t\tstrings.HasPrefix(p, \"internal/controller/\") ||\n\t\tstrings.HasPrefix(p, \"internal/webhook/\") ||\n\t\t(strings.HasPrefix(p, \"api/\") && strings.HasSuffix(p, \"_types.go\")) {\n\t\treturn true\n\t}\n\n\t// Test files (important for breaking changes)\n\tif strings.HasPrefix(p, \"test/\") && (strings.HasSuffix(p, \"_test.go\") ||\n\t\tstrings.HasSuffix(p, \".go\")) {\n\t\treturn true\n\t}\n\n\t// Important config files (not generated)\n\tif strings.HasPrefix(p, \"config/\") {\n\t\t// Include kustomization files and important config\n\t\tif strings.HasSuffix(p, \"kustomization.yaml\") ||\n\t\t\tstrings.HasPrefix(p, \"config/default/\") ||\n\t\t\tstrings.HasPrefix(p, \"config/manager/\") ||\n\t\t\tstrings.HasPrefix(p, \"config/webhook/\") ||\n\t\t\tstrings.HasPrefix(p, \"config/certmanager/\") ||\n\t\t\tstrings.HasPrefix(p, \"config/prometheus/\") ||\n\t\t\tstrings.HasPrefix(p, \"config/network-policy/\") {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Priority: lower number = earlier.\n// 0: go.mod (dependencies)\n// 1: Makefile (build automation)\n// 2: Dockerfile (container images)\n// 3: Core code (cmd/, controllers/, api/*_types.go, internal/)\n// 4: Critical config (config/default, config/manager)\n// 5: Webhook & security config (config/webhook, config/certmanager)\n// 6: Other config (config/*)\n// 7: Tests\n// 9: fallback\nfunc filePriority(p string) int {\n\tswitch {\n\tcase p == \"go.mod\":\n\t\treturn 0\n\tcase p == \"Makefile\":\n\t\treturn 1\n\tcase p == \"Dockerfile\":\n\t\treturn 2\n\tcase strings.HasPrefix(p, \"cmd/\"),\n\t\tstrings.HasPrefix(p, \"controllers/\"),\n\t\tstrings.HasPrefix(p, \"internal/controller/\"),\n\t\tstrings.HasPrefix(p, \"internal/webhook/\"),\n\t\t(strings.HasPrefix(p, \"api/\") && strings.HasSuffix(p, \"_types.go\")):\n\t\treturn 3\n\tcase strings.HasPrefix(p, \"config/default/\"),\n\t\tstrings.HasPrefix(p, \"config/manager/\"),\n\t\tp == \"config/default/kustomization.yaml\",\n\t\tp == \"config/manager/kustomization.yaml\":\n\t\treturn 4\n\tcase strings.HasPrefix(p, \"config/webhook/\"),\n\t\tstrings.HasPrefix(p, \"config/certmanager/\"),\n\t\tstrings.HasPrefix(p, \"config/prometheus/\"),\n\t\tstrings.HasPrefix(p, \"config/network-policy/\"):\n\t\treturn 5\n\tcase strings.HasPrefix(p, \"config/\"):\n\t\treturn 6\n\tcase strings.HasPrefix(p, \"test/\"):\n\t\treturn 7\n\tdefault:\n\t\treturn 9\n\t}\n}\n\n//nolint:lll\nvar (\n\treFlags       = regexp.MustCompile(`(?i)--(leader-elect|metrics-bind-address|health-probe-bind-address|\\bzap|secure-port|bind-address)`)\n\treGo          = regexp.MustCompile(`(?i)^(?:\\+|\\-)\\s*(package|import|type|func|const|var|//\\+kubebuilder:|//go:(?:build|generate)|return|if\\s+err|log\\.|fmt\\.|errors?\\.|client\\.|ctrl\\.|manager|scheme|requeue|context\\.|SetupWithManager|Reconcile|reconcile\\.Result)`)\n\treYAMLKey     = regexp.MustCompile(`(?i)(apiVersion:|kind:|metadata:|name:|namespace:|image:|command:|args:|env:|resources:|limits:|requests:|ports:|securityContext:|readOnlyRootFilesystem|runAsNonRoot|seccompProfile|allowPrivilegeEscalation|capabilities|livenessProbe|readinessProbe|namePrefix:|commonLabels:|bases:|patches:|replicas:)`)\n\treDocker      = regexp.MustCompile(`(?i)^(?:\\+|\\-)\\s*(FROM|ARG|ENV|RUN|ENTRYPOINT|CMD|COPY|ADD|USER|WORKDIR)\\b`)\n\treMakeLine    = regexp.MustCompile(`(?i)^(?:\\+|\\-)\\s*([A-Z0-9_]+)\\s*[:?+]?=\\s*|^(?:\\+|\\-)\\s*(manifests|generate|fmt|vet|lint-fix|docker-build|test|install|uninstall|deploy|undeploy|build-installer|controller-gen|kustomize)\\b`)\n\treKubebuilder = regexp.MustCompile(`(?i)^(?:\\+|\\-)\\s*(\\/\\/\\+kubebuilder:|kubebuilder\\s+(init|create|edit)|controller-runtime|sigs\\.k8s\\.io|k8s\\.io\\/api|k8s\\.io\\/apimachinery)`)\n)\n\n// keepGoModLine returns true for +/- go.mod lines we want to retain.\n// Keep: module/go/toolchain, replace, require lines without \"// indirect\", and block delimiters.\nfunc keepGoModLine(s string) bool {\n\tif len(s) == 0 || (s[0] != '+' && s[0] != '-') {\n\t\treturn false\n\t}\n\tt := strings.TrimSpace(s[1:]) // strip +/- then trim\n\tswitch {\n\tcase strings.HasPrefix(t, \"module \"):\n\t\treturn true\n\tcase strings.HasPrefix(t, \"go \"):\n\t\treturn true\n\tcase strings.HasPrefix(t, \"toolchain \"):\n\t\treturn true\n\tcase strings.HasPrefix(t, \"replace \"):\n\t\treturn true\n\tcase strings.HasPrefix(t, \"require \") && !strings.Contains(t, \"// indirect\"):\n\t\treturn true\n\tcase t == \"require (\" || t == \")\": // keep block delimiters for readability\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// Decide if a +/- line is interesting based on the file path.\nfunc interestingLine(path, line string) bool {\n\tif len(line) == 0 || (line[0] != '+' && line[0] != '-') {\n\t\treturn false\n\t}\n\tswitch {\n\tcase strings.HasSuffix(path, \".go\"):\n\t\treturn reGo.MatchString(line) || reKubebuilder.MatchString(line)\n\tcase strings.HasSuffix(path, \".yaml\") || strings.HasSuffix(path, \".yml\"):\n\t\treturn reYAMLKey.MatchString(line) || reFlags.MatchString(line) || reKubebuilder.MatchString(line)\n\tcase path == \"Makefile\":\n\t\treturn reMakeLine.MatchString(line) || reKubebuilder.MatchString(line)\n\tcase path == \"Dockerfile\":\n\t\treturn reDocker.MatchString(line)\n\tcase strings.HasSuffix(path, \"kustomization.yaml\"):\n\t\t// Kustomization files are critical for Kubebuilder config\n\t\treturn true\n\tdefault:\n\t\t// Unknown text files: keep Kubebuilder-related lines and obvious flag changes\n\t\treturn reFlags.MatchString(line) || reKubebuilder.MatchString(line)\n\t}\n}\n\n// Curated unified=0 diff: keep hunk headers + filtered +/- lines.\n// For go.mod keep only direct requires and key headers (still capped).\nfunc selectedDiff(base, head, path string, maxLines int) string {\n\tcmd := exec.Command(\"git\", \"diff\", \"--no-color\", \"-w\", \"--unified=0\", base+\"..\"+head, \"--\", path)\n\tout, _ := cmd.Output()\n\tif len(out) == 0 {\n\t\treturn \"\"\n\t}\n\n\tsc := bufio.NewScanner(bytes.NewReader(out))\n\tlines := 0\n\tvar b strings.Builder\n\n\tif path == \"go.mod\" {\n\t\tfor sc.Scan() {\n\t\t\ts := sc.Text()\n\t\t\tif strings.HasPrefix(s, \"@@\") {\n\t\t\t\tb.WriteString(s + \"\\n\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif keepGoModLine(s) {\n\t\t\t\tb.WriteString(s + \"\\n\")\n\t\t\t\tlines++\n\t\t\t\tif lines >= maxLines {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn strings.TrimSpace(b.String())\n\t}\n\n\tfor sc.Scan() {\n\t\ts := sc.Text()\n\t\tif strings.HasPrefix(s, \"@@\") {\n\t\t\tb.WriteString(s + \"\\n\")\n\t\t\tcontinue\n\t\t}\n\t\tif len(s) == 0 || (s[0] != '+' && s[0] != '-') {\n\t\t\tcontinue\n\t\t}\n\t\tif interestingLine(path, s) {\n\t\t\tb.WriteString(s + \"\\n\")\n\t\t\tlines++\n\t\t\tif lines >= maxLines {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn strings.TrimSpace(b.String())\n}\n\nfunc concatSelectedDiffs(base, head string, files []string, perFileLineCap, totalByteCap int) string {\n\tvar b strings.Builder\n\n\t// Global budget: prefer the passed-in cap if >0, else default.\n\tremaining := totalByteCap\n\tif remaining <= 0 {\n\t\tremaining = selectedDiffTotalCap\n\t}\n\n\t// Filter and prioritize candidates\n\tcandidates := make([]string, 0, len(files))\n\tfor _, p := range files {\n\t\tif importantFile(p) {\n\t\t\tcandidates = append(candidates, p)\n\t\t}\n\t}\n\tslices.SortStableFunc(candidates, func(a, b string) int {\n\t\tpi, pj := filePriority(a), filePriority(b)\n\t\tif pi != pj {\n\t\t\treturn pi - pj\n\t\t}\n\t\treturn strings.Compare(a, b) // stable alphabetical within same priority\n\t})\n\n\t// Emit diffs until the global budget is hit\n\tfor _, p := range candidates {\n\t\t// Per-file line budget: use param if >0, else default; ensure go.mod gets at least its larger cap.\n\t\tperCap := perFileLineCap\n\t\tif perCap <= 0 {\n\t\t\tperCap = selectedDiffLinesPerFile\n\t\t}\n\t\tif p == \"go.mod\" && perCap < selectedDiffLinesGoMod {\n\t\t\tperCap = selectedDiffLinesGoMod\n\t\t}\n\n\t\tdiff := selectedDiff(base, head, p, perCap)\n\t\tif diff == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tsection := \"----- BEGIN SELECTED DIFF \" + p + \" -----\\n\" + diff + \"\\n----- END SELECTED DIFF \" + p + \" -----\\n\\n\"\n\t\tif len(section) > remaining {\n\t\t\tif remaining <= 0 {\n\t\t\t\tb.WriteString(\"\\n... [global diff budget reached] ...\\n\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// Trim last section to fit remaining budget\n\t\t\tcut := min(remaining, len(section))\n\t\t\tb.WriteString(section[:cut])\n\t\t\tb.WriteString(\"\\n... [global diff budget reached] ...\\n\")\n\t\t\tbreak\n\t\t}\n\n\t\tb.WriteString(section)\n\t\tremaining -= len(section)\n\t}\n\n\tout := strings.TrimSpace(b.String())\n\tif out == \"\" {\n\t\treturn \"<no selected diffs produced>\"\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/open_gh_issue_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Open GitHub Issue Helpers\", func() {\n\tDescribe(\"excludedFromDiff\", func() {\n\t\tIt(\"should exclude unimportant files\", func() {\n\t\t\tExpect(excludedFromDiff(\"PROJECT\")).To(BeTrue())\n\t\t\tExpect(excludedFromDiff(\"README.md\")).To(BeTrue())\n\t\t\tExpect(excludedFromDiff(\"hack/boilerplate.go.txt\")).To(BeTrue())\n\t\t\tExpect(excludedFromDiff(\"go.sum\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should include important files\", func() {\n\t\t\tExpect(excludedFromDiff(\"go.mod\")).To(BeFalse())\n\t\t\tExpect(excludedFromDiff(\"Makefile\")).To(BeFalse())\n\t\t\tExpect(excludedFromDiff(\"Dockerfile\")).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"importantFile\", func() {\n\t\tIt(\"should identify important core files\", func() {\n\t\t\tExpect(importantFile(\"go.mod\")).To(BeTrue())\n\t\t\tExpect(importantFile(\"Makefile\")).To(BeTrue())\n\t\t\tExpect(importantFile(\"cmd/main.go\")).To(BeTrue())\n\t\t\tExpect(importantFile(\"api/v1/captain_types.go\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should exclude generated and unimportant files\", func() {\n\t\t\tExpect(importantFile(\"PROJECT\")).To(BeFalse())\n\t\t\tExpect(importantFile(\"hack/boilerplate.go.txt\")).To(BeFalse())\n\t\t\tExpect(importantFile(\"config/crd/bases/captain.yaml\")).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"filePriority\", func() {\n\t\tIt(\"should prioritize files correctly\", func() {\n\t\t\tExpect(filePriority(\"go.mod\")).To(Equal(0))\n\t\t\tExpect(filePriority(\"Makefile\")).To(Equal(1))\n\t\t\tExpect(filePriority(\"Dockerfile\")).To(Equal(2))\n\t\t\tExpect(filePriority(\"cmd/main.go\")).To(Equal(3))\n\t\t\tExpect(filePriority(\"config/default/kustomization.yaml\")).To(Equal(4))\n\t\t})\n\t})\n\n\tDescribe(\"FirstURL\", func() {\n\t\tIt(\"should extract URLs from text\", func() {\n\t\t\tExpect(FirstURL(\"https://github.com/user/repo\")).To(Equal(\"https://github.com/user/repo\"))\n\t\t\tExpect(FirstURL(\"Check https://example.com here\")).To(Equal(\"https://example.com\"))\n\t\t\tExpect(FirstURL(\"no links here\")).To(Equal(\"\"))\n\t\t})\n\t})\n\n\tDescribe(\"IssueNumberFromURL\", func() {\n\t\tIt(\"should extract issue numbers\", func() {\n\t\t\tExpect(IssueNumberFromURL(\"https://github.com/user/repo/issues/123\")).To(Equal(\"123\"))\n\t\t\tExpect(IssueNumberFromURL(\"https://github.com/user/repo/pull/456\")).To(Equal(\"456\"))\n\t\t})\n\t})\n\n\tDescribe(\"bulletList\", func() {\n\t\tIt(\"should format bullet lists\", func() {\n\t\t\tExpect(bulletList([]string{})).To(Equal(\"<none>\"))\n\t\t\tExpect(bulletList([]string{\"item1\"})).To(Equal(\"- item1\"))\n\t\t\tExpect(bulletList([]string{\"item1\", \"item2\"})).To(Equal(\"- item1\\n- item2\"))\n\t\t})\n\t})\n\n\tDescribe(\"keepGoModLine\", func() {\n\t\tIt(\"should keep important go.mod lines\", func() {\n\t\t\tExpect(keepGoModLine(\"+module github.com/user/repo\")).To(BeTrue())\n\t\t\tExpect(keepGoModLine(\"+go 1.21\")).To(BeTrue())\n\t\t\tExpect(keepGoModLine(\"+require example.com/pkg v1.0.0\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should skip indirect dependencies\", func() {\n\t\t\tExpect(keepGoModLine(\"+require example.com/pkg v1.0.0 // indirect\")).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"interestingLine\", func() {\n\t\tIt(\"should detect interesting Go lines\", func() {\n\t\t\tExpect(interestingLine(\"main.go\", \"+import \\\"context\\\"\")).To(BeTrue())\n\t\t\tExpect(interestingLine(\"controller.go\", \"+//+kubebuilder:rbac:groups=apps\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should detect interesting YAML lines\", func() {\n\t\t\tExpect(interestingLine(\"manager.yaml\", \"+apiVersion: apps/v1\")).To(BeTrue())\n\t\t\tExpect(interestingLine(\"config.yaml\", \"+image: controller:latest\")).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should skip uninteresting lines\", func() {\n\t\t\tExpect(interestingLine(\"main.go\", \"+x := 1\")).To(BeFalse())\n\t\t\tExpect(interestingLine(\"config.yaml\", \"+# comment\")).To(BeFalse())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/helpers/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestCommand(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"alpha command: update helpers suite\")\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/update/helpers\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nconst (\n\tfromVersion           = \"v4.5.2\"\n\ttoVersion             = \"v4.6.0\"\n\ttoVersionWithConflict = \"v4.7.0\"\n\n\t// Memcached operator + all plugins integration test (mirrors testdata generate.sh with-plugins)\n\t// Upgrade from a fixed past version to current (latest release when test runs).\n\tmemcachedFromVersion = \"v4.11.1\"\n\n\t// Custom registry value added to Helm values.yaml; must be preserved after alpha update.\n\tmemcachedHelmCustomRegistry = \"myregistry.io/custom/controller\"\n\n\t// Regex matching the commented Affinity block in deploy-image memcached controller (any whitespace).\n\t// Used with plugin util ReplaceRegexInFile so we can use backtick replacement string.\n\tmemcachedAffinityCommentedRegex = `(?s)// TODO\\(user\\): Uncomment the following code to configure the nodeAffinity expression.*?//\\s*\\},`\n\n\t// Uncommented Affinity block (customization to be preserved by alpha update).\n\tmemcachedAffinityUncommented = `// Node affinity for multi-arch (customization preserved by update)\n\t\t\t\t\tAffinity: &corev1.Affinity{\n\t\t\t\t\t\tNodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t\t\tRequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t\t\t\tNodeSelectorTerms: []corev1.NodeSelectorTerm{{\n\t\t\t\t\t\t\t\t\tMatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t\t\t\t\t\t{Key: \"kubernetes.io/arch\", Operator: \"In\", Values: []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"}},\n\t\t\t\t\t\t\t\t\t\t{Key: \"kubernetes.io/os\", Operator: \"In\", Values: []string{\"linux\"}},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},`\n\n\tcontrollerImplementation = `// Fetch the TestOperator instance\n\ttestOperator := &webappv1.TestOperator{}\n\terr := r.Get(ctx, req.NamespacedName, testOperator)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\tlog.Info(\"testOperator resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\tlog.Error(err, \"Failed to get testOperator\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tlog.Info(\"testOperator reconciled\")`\n\n\tcustomField = `// +kubebuilder:validation:Minimum=0\n// +kubebuilder:validation:Maximum=3\n// +kubebuilder:default=1\nSize int32 ` + \"`json:\\\"size,omitempty\\\"`\" + `\n`\n)\n\nvar _ = Describe(\"kubebuilder\", func() {\n\tContext(\"alpha update\", func() {\n\t\tvar (\n\t\t\tpathBinFromVersion string\n\t\t\tkbc                *utils.TestContext\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tBy(\"setting up test context with binary build from source\")\n\t\t\tkbc, err = utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t\tpathBinFromVersion, err = downloadKubebuilderVersion(fromVersion)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcmd := exec.Command(pathBinFromVersion, \"init\", \"--domain\", \"example.com\", \"--repo\",\n\t\t\t\t\"github.com/example/test-operator\")\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\toutput, err := cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"init failed: %s\", output))\n\n\t\t\tcmd = exec.Command(pathBinFromVersion, \"create\", \"api\", \"--group\", \"webapp\", \"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestOperator\", \"--resource\", \"--controller\")\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create api failed: %s\", output))\n\t\t\tExpect(kbc.Make(\"generate\", \"manifests\")).To(Succeed())\n\n\t\t\tupdateAPI(kbc.Dir)\n\t\t\tupdateController(kbc.Dir)\n\t\t\tinitializeGitRepo(kbc.Dir)\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"cleaning up test artifacts\")\n\t\t\t_ = os.RemoveAll(filepath.Dir(pathBinFromVersion))\n\t\t\t_ = os.RemoveAll(kbc.Dir)\n\t\t\tkbc.Destroy()\n\t\t})\n\n\t\tIt(\"should update project from v4.5.2 to v4.6.0 without conflicts\", func() {\n\t\t\tBy(\"running alpha update from v4.5.2 to v4.6.0\")\n\t\t\tcmd := exec.Command(\n\t\t\t\tkbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", fromVersion,\n\t\t\t\t\"--to-version\", toVersion,\n\t\t\t\t\"--from-branch\", \"main\",\n\t\t\t)\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\tout, err := kbc.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(out))\n\n\t\t\tBy(\"checking that custom code is preserved\")\n\t\t\tvalidateCustomCodePreservation(kbc.Dir)\n\n\t\t\tBy(\"checking that no conflict markers are present in the project files\")\n\t\t\tExpect(hasConflictMarkers(kbc.Dir)).To(BeFalse())\n\n\t\t\tBy(\"checking that go module is upgraded\")\n\t\t\tvalidateCommonGoModule(kbc.Dir)\n\n\t\t\tBy(\"checking that Makefile is updated\")\n\t\t\tvalidateMakefileContent(kbc.Dir)\n\n\t\t\tBy(\"checking temporary branches were cleaned up locally\")\n\t\t\toutRefs, err := exec.Command(\"git\", \"-C\", kbc.Dir, \"for-each-ref\",\n\t\t\t\t\"--format=%(refname:short)\", \"refs/heads\").CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(outRefs))\n\t\t\tExpect(string(outRefs)).NotTo(ContainSubstring(\"tmp-ancestor\"))\n\t\t\tExpect(string(outRefs)).NotTo(ContainSubstring(\"tmp-original\"))\n\t\t\tExpect(string(outRefs)).NotTo(ContainSubstring(\"tmp-upgrade\"))\n\t\t\tExpect(string(outRefs)).NotTo(ContainSubstring(\"tmp-merge\"))\n\t\t})\n\n\t\tIt(\"should update project from v4.5.2 to v4.7.0 with --force flag and create conflict markers\", func() {\n\t\t\tBy(\"modifying original Makefile to use CONTROLLER_TOOLS_VERSION v0.17.3\")\n\t\t\tmodifyMakefileControllerTools(kbc.Dir, \"v0.17.3\")\n\n\t\t\tBy(\"running alpha update with --force (default behavior is squash)\")\n\t\t\tcmd := exec.Command(\n\t\t\t\tkbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", fromVersion,\n\t\t\t\t\"--to-version\", toVersionWithConflict,\n\t\t\t\t\"--from-branch\", \"main\",\n\t\t\t\t\"--force\",\n\t\t\t)\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\tout, err := kbc.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(out))\n\n\t\t\tBy(\"checking that custom code is preserved\")\n\t\t\tvalidateCustomCodePreservation(kbc.Dir)\n\n\t\t\tBy(\"checking that conflict markers are present in the project files\")\n\t\t\tExpect(hasConflictMarkers(kbc.Dir)).To(BeTrue())\n\n\t\t\tBy(\"checking that go module is upgraded to expected versions\")\n\t\t\tvalidateCommonGoModule(kbc.Dir)\n\n\t\t\tBy(\"checking that Makefile is updated and has conflict between old and new versions in Makefile\")\n\t\t\tmakefilePath := filepath.Join(kbc.Dir, \"Makefile\")\n\t\t\tcontent, err := os.ReadFile(makefilePath)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to read Makefile after update\")\n\t\t\tmakefileStr := string(content)\n\n\t\t\t// Should update to the new version\n\t\t\tExpect(makefileStr).To(ContainSubstring(`GOLANGCI_LINT_VERSION ?= v2.1.6`))\n\n\t\t\t// The original project was scaffolded with v0.17.2 (from v4.5.2).\n\t\t\t// The user manually updated it to v0.17.3.\n\t\t\t// The target upgrade version (v4.7.0) introduces v0.18.0.\n\t\t\t//\n\t\t\t// Because both the user's version (v0.17.3) and the scaffold version (v0.18.0) differ,\n\t\t\t// we expect Git to insert conflict markers around this line in the Makefile:\n\t\t\t//\n\t\t\t// <<<<<<< HEAD\n\t\t\t// CONTROLLER_TOOLS_VERSION ?= v0.18.0\n\t\t\t// =======\n\t\t\t// CONTROLLER_TOOLS_VERSION ?= v0.17.3\n\t\t\t// >>>>>>> tmp-original-*\n\t\t\tExpect(makefileStr).To(ContainSubstring(\"<<<<<<<\"),\n\t\t\t\t\"Expected conflict marker <<<<<<< in Makefile\")\n\t\t\tExpect(makefileStr).To(ContainSubstring(\"=======\"),\n\t\t\t\t\"Expected conflict separator ======= in Makefile\")\n\t\t\tExpect(makefileStr).To(ContainSubstring(\">>>>>>>\"),\n\t\t\t\t\"Expected conflict marker >>>>>>> in Makefile\")\n\t\t\tExpect(makefileStr).To(ContainSubstring(\"CONTROLLER_TOOLS_VERSION ?= v0.17.3\"),\n\t\t\t\t\"Expected original user version in conflict\")\n\t\t\tExpect(makefileStr).To(ContainSubstring(\"CONTROLLER_TOOLS_VERSION ?= v0.18.0\"),\n\t\t\t\t\"Expected latest scaffold version in conflict\")\n\n\t\t\tBy(\"checking that the output branch (squashed) exists and is 1 commit ahead of main\")\n\t\t\tprBranch := \"kubebuilder-update-from-\" + fromVersion + \"-to-\" + toVersionWithConflict\n\n\t\t\tgit := func(args ...string) ([]byte, error) {\n\t\t\t\tcmd := exec.Command(\"git\", args...)\n\t\t\t\tcmd.Dir = kbc.Dir\n\t\t\t\treturn cmd.CombinedOutput()\n\t\t\t}\n\n\t\t\tBy(\"checking that the squashed branch exists\")\n\t\t\t_, err = git(\"rev-parse\", \"--verify\", prBranch)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking that exactly one squashed commit ahead of main\")\n\t\t\tcount, err := git(\"rev-list\", \"--count\", prBranch, \"^main\")\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(count))\n\t\t\tExpect(strings.TrimSpace(string(count))).To(Equal(\"1\"))\n\n\t\t\tBy(\"checking commit message of the squashed branch\")\n\t\t\tmsg, err := git(\"log\", \"-1\", \"--pretty=%B\", prBranch)\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(msg))\n\t\t\texpected := helpers.ConflictCommitMessage(fromVersion, toVersionWithConflict)\n\t\t\tExpect(string(msg)).To(ContainSubstring(expected))\n\t\t})\n\n\t\tIt(\"should stop when updating the project from v4.5.2 to v4.7.0 without the flag force\", func() {\n\t\t\tBy(\"running alpha update without --force flag\")\n\t\t\tcmd := exec.Command(\n\t\t\t\tkbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", fromVersion,\n\t\t\t\t\"--to-version\", toVersionWithConflict,\n\t\t\t\t\"--from-branch\", \"main\",\n\t\t\t)\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\tout, err := kbc.Run(cmd)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(string(out)).To(ContainSubstring(\"merge stopped due to conflicts\"))\n\n\t\t\tBy(\"validating that merge stopped with conflicts requiring manual resolution\")\n\t\t\tvalidateConflictState(kbc.Dir)\n\n\t\t\tBy(\"checking that custom code is preserved\")\n\t\t\tvalidateCustomCodePreservation(kbc.Dir)\n\n\t\t\tBy(\"checking that go module is upgraded\")\n\t\t\tvalidateCommonGoModule(kbc.Dir)\n\t\t})\n\n\t\tIt(\"should preserve specified paths from base when squashing (e.g., .github/workflows)\", func() {\n\t\t\tBy(\"adding a workflow on main branch that should be preserved\")\n\t\t\twfDir := filepath.Join(kbc.Dir, \".github\", \"workflows\")\n\t\t\tExpect(os.MkdirAll(wfDir, 0o755)).To(Succeed())\n\t\t\twf := filepath.Join(wfDir, \"ci.yml\")\n\t\t\tExpect(os.WriteFile(wf, []byte(\"name: KEEP_ME\\n\"), 0o644)).To(Succeed())\n\n\t\t\tgit := func(args ...string) {\n\t\t\t\tc := exec.Command(\"git\", args...)\n\t\t\t\tc.Dir = kbc.Dir\n\t\t\t\to, e := c.CombinedOutput()\n\t\t\t\tExpect(e).NotTo(HaveOccurred(), string(o))\n\t\t\t}\n\t\t\tgit(\"add\", \".github/workflows/ci.yml\")\n\t\t\tgit(\"commit\", \"-m\", \"add ci workflow\")\n\n\t\t\tBy(\"running update (default squash) with --restore-path\")\n\t\t\tcmd := exec.Command(\n\t\t\t\tkbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", fromVersion,\n\t\t\t\t\"--to-version\", toVersion,\n\t\t\t\t\"--from-branch\", \"main\",\n\t\t\t\t\"--restore-path\", \".github/workflows\",\n\t\t\t)\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\tout, err := kbc.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(out))\n\n\t\t\tBy(\"workflow content is preserved on output branch\")\n\t\t\tdata, err := os.ReadFile(wf)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(data)).To(ContainSubstring(\"KEEP_ME\"))\n\t\t})\n\n\t\tIt(\"should succeed with no action when from-version and to-version are the same\", func() {\n\t\t\tcmd := exec.Command(kbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", fromVersion,\n\t\t\t\t\"--to-version\", fromVersion,\n\t\t\t\t\"--from-branch\", \"main\")\n\t\t\toutput, err := kbc.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(output)).To(ContainSubstring(\"already uses the specified version\"))\n\t\t\tExpect(string(output)).To(ContainSubstring(\"No action taken\"))\n\t\t})\n\t})\n\n\t// Scaffolding (mock) is done with v4.11.1; the alpha update step is run with the current\n\t// binary (kbc.BinaryName from PATH, e.g. from make install) so we test current code changes.\n\tContext(\"alpha update with memcached operator and all plugins (Grafana, Helm, deploy-image)\", func() {\n\t\tvar (\n\t\t\tpathBinV4111 string\n\t\t\tkbc          *utils.TestContext\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tBy(\"setting up test context (scaffold with v4.11.1; alpha update will use current binary)\")\n\t\t\tkbc, err = utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t\tBy(\"downloading kubebuilder release \" + memcachedFromVersion)\n\t\t\tpathBinV4111, err = downloadKubebuilderVersion(memcachedFromVersion)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tkb := pathBinV4111\n\t\t\tdir := kbc.Dir\n\n\t\t\tBy(\"initializing project (go/v4) as in generate.sh with-plugins\")\n\t\t\tcmd := exec.Command(kb, \"init\", \"--domain\", \"testproject.org\", \"--repo\",\n\t\t\t\t\"github.com/example/memcached-operator\", \"--plugins=go/v4\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err := cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"init failed: %s\", output))\n\t\t\t// Note: v4.11.1 does not support --namespaced on init/edit; we scaffold without it for compatibility\n\n\t\t\tBy(\"creating Memcached API with deploy-image plugin (same args as generate.sh)\")\n\t\t\tcmd = exec.Command(kb, \"create\", \"api\",\n\t\t\t\t\"--group\", \"example.com\", \"--version\", \"v1alpha1\", \"--kind\", \"Memcached\",\n\t\t\t\t\"--image=memcached:1.6.26-alpine3.19\",\n\t\t\t\t\"--image-container-command=memcached,--memory-limit=64,-o,modern,-v\",\n\t\t\t\t\"--image-container-port=11211\", \"--run-as-user=1001\",\n\t\t\t\t\"--plugins=deploy-image/v1-alpha\", \"--make=false\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create api Memcached failed: %s\", output))\n\n\t\t\tBy(\"creating webhook for Memcached (programmatic-validation)\")\n\t\t\tcmd = exec.Command(kb, \"create\", \"webhook\",\n\t\t\t\t\"--group\", \"example.com\", \"--version\", \"v1alpha1\", \"--kind\", \"Memcached\",\n\t\t\t\t\"--programmatic-validation\", \"--make=false\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create webhook Memcached failed: %s\", output))\n\n\t\t\tBy(\"adding custom implementation to Memcached controller (uncomment Affinity block)\")\n\t\t\tmemcachedControllerPath := filepath.Join(dir, \"internal\", \"controller\", \"memcached_controller.go\")\n\t\t\tExpect(util.ReplaceRegexInFile(memcachedControllerPath, memcachedAffinityCommentedRegex, memcachedAffinityUncommented)).\n\t\t\t\tTo(Succeed(), \"failed to uncomment Affinity in memcached_controller.go\")\n\n\t\t\tBy(\"creating Busybox API with deploy-image plugin\")\n\t\t\tcmd = exec.Command(kb, \"create\", \"api\",\n\t\t\t\t\"--group\", \"example.com\", \"--version\", \"v1alpha1\", \"--kind\", \"Busybox\",\n\t\t\t\t\"--image=busybox:1.36.1\", \"--plugins=deploy-image/v1-alpha\", \"--make=false\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create api Busybox failed: %s\", output))\n\n\t\t\tBy(\"creating Wordpress v1 and v2 with conversion webhook\")\n\t\t\tcmd = exec.Command(kb, \"create\", \"api\", \"--group\", \"example.com\", \"--version\", \"v1\", \"--kind\", \"Wordpress\",\n\t\t\t\t\"--controller\", \"--resource\", \"--make=false\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create api Wordpress v1 failed: %s\", output))\n\t\t\tcmd = exec.Command(kb, \"create\", \"api\", \"--group\", \"example.com\", \"--version\", \"v2\", \"--kind\", \"Wordpress\",\n\t\t\t\t\"--controller=false\", \"--resource\", \"--make=false\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create api Wordpress v2 failed: %s\", output))\n\t\t\tcmd = exec.Command(kb, \"create\", \"webhook\", \"--group\", \"example.com\", \"--version\", \"v1\", \"--kind\", \"Wordpress\",\n\t\t\t\t\"--conversion\", \"--make=false\", \"--spoke\", \"v2\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"create webhook Wordpress conversion failed: %s\", output))\n\n\t\t\tBy(\"editing project with Grafana plugin\")\n\t\t\tcmd = exec.Command(kb, \"edit\", \"--plugins=grafana.kubebuilder.io/v1-alpha\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"edit grafana failed: %s\", output))\n\n\t\t\tBy(\"running make all\")\n\t\t\tExpect(kbc.Make(\"all\")).To(Succeed())\n\n\t\t\tBy(\"editing project with Helm plugin\")\n\t\t\tcmd = exec.Command(kb, \"edit\", \"--plugins=helm.kubebuilder.io/v2-alpha\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"edit helm failed: %s\", output))\n\n\t\t\tBy(\"customizing Helm chart values (custom registry to be preserved after update)\")\n\t\t\tvaluesPath := filepath.Join(dir, \"dist\", \"chart\", \"values.yaml\")\n\t\t\tExpect(util.ReplaceInFile(valuesPath, \"repository: controller\", \"repository: \"+memcachedHelmCustomRegistry)).\n\t\t\t\tTo(Succeed(), \"failed to set custom registry in dist/chart/values.yaml\")\n\n\t\t\tBy(\"editing project with Auto Update plugin\")\n\t\t\tcmd = exec.Command(kb, \"edit\", \"--plugins=autoupdate.kubebuilder.io/v1-alpha\", \"--use-gh-models\")\n\t\t\tcmd.Dir = dir\n\t\t\toutput, err = cmd.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"edit autoupdate failed: %s\", output))\n\n\t\t\tBy(\"running go mod tidy\")\n\t\t\tgoTidy := exec.Command(\"go\", \"mod\", \"tidy\")\n\t\t\tgoTidy.Dir = dir\n\t\t\toutput, err = goTidy.CombinedOutput()\n\t\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"go mod tidy failed: %s\", output))\n\n\t\t\tBy(\"initializing git and committing\")\n\t\t\tinitializeGitRepo(dir)\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"cleaning up test artifacts\")\n\t\t\tif pathBinV4111 != \"\" {\n\t\t\t\t_ = os.RemoveAll(filepath.Dir(pathBinV4111))\n\t\t\t}\n\t\t\tif kbc != nil {\n\t\t\t\t_ = os.RemoveAll(kbc.Dir)\n\t\t\t\tkbc.Destroy()\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should update from v4.11.1 to current (latest) and regenerate Helm, Grafana, and deploy-image scaffolds\", func() {\n\t\t\t// kbc.BinaryName is the current binary (from PATH / make install); we test current code.\n\t\t\tBy(\"running alpha update from \" + memcachedFromVersion + \" to current (latest) with --force (temp dir: kbc.Dir)\")\n\t\t\tcmd := exec.Command(\n\t\t\t\tkbc.BinaryName, \"alpha\", \"update\",\n\t\t\t\t\"--from-version\", memcachedFromVersion,\n\t\t\t\t\"--from-branch\", \"main\",\n\t\t\t\t\"--force\",\n\t\t\t)\n\t\t\tcmd.Dir = kbc.Dir\n\t\t\tout, err := kbc.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), string(out))\n\n\t\t\t// With --force, update completes even with conflicts; result is on the output branch (squashed).\n\t\t\tprojectDir := kbc.Dir\n\n\t\t\tBy(\"checking that no unexpected conflict markers remain in key plugin outputs\")\n\t\t\t// Allow conflicts only in Makefile or go.mod if versions changed; plugin scaffolds should be clean\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"dist/chart\")\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"grafana\")\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"api/v1alpha1\")\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"internal/controller\")\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"config/samples\")\n\t\t\texpectNoConflictMarkersInPath(projectDir, \"config/rbac\")\n\n\t\t\tBy(\"asserting Helm chart was properly regenerated (deploy-image resources included)\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/Chart.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/values.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/templates/crd/memcacheds.example.com.testproject.org.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/templates/crd/busyboxes.example.com.testproject.org.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/templates/crd/wordpresses.example.com.testproject.org.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/templates/rbac/memcached-admin-role.yaml\")\n\t\t\texpectFileExists(projectDir, \"dist/chart/templates/rbac/busybox-admin-role.yaml\")\n\n\t\t\tBy(\"asserting custom Helm values (registry) were preserved after regeneration\")\n\t\t\tvaluesContent, err := os.ReadFile(filepath.Join(projectDir, \"dist/chart/values.yaml\"))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(valuesContent)).To(ContainSubstring(memcachedHelmCustomRegistry),\n\t\t\t\t\"custom registry in values.yaml must be preserved by alpha update\")\n\n\t\t\tBy(\"asserting Grafana dashboards were properly regenerated\")\n\t\t\texpectFileExists(projectDir, \"grafana/controller-runtime-metrics.json\")\n\t\t\texpectFileExists(projectDir, \"grafana/controller-resources-metrics.json\")\n\n\t\t\tBy(\"asserting deploy-image scaffolds (Memcached, Busybox) were properly regenerated\")\n\t\t\texpectFileExists(projectDir, \"api/v1alpha1/memcached_types.go\")\n\t\t\texpectFileExists(projectDir, \"api/v1alpha1/busybox_types.go\")\n\t\t\texpectFileExists(projectDir, \"internal/controller/memcached_controller.go\")\n\t\t\texpectFileExists(projectDir, \"internal/controller/busybox_controller.go\")\n\t\t\texpectFileExists(projectDir, \"config/samples/example.com_v1alpha1_memcached.yaml\")\n\t\t\texpectFileExists(projectDir, \"config/samples/example.com_v1alpha1_busybox.yaml\")\n\t\t\texpectFileExists(projectDir, \"config/rbac/memcached_admin_role.yaml\")\n\t\t\texpectFileExists(projectDir, \"config/rbac/busybox_admin_role.yaml\")\n\n\t\t\tBy(\"asserting custom Memcached controller implementation (uncommented Affinity) was preserved\")\n\t\t\tmemcachedControllerContent, err := os.ReadFile(filepath.Join(projectDir, \"internal/controller/memcached_controller.go\"))\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"failed to read memcached_controller.go\")\n\t\t\tmemcachedControllerStr := string(memcachedControllerContent)\n\t\t\tExpect(memcachedControllerStr).To(ContainSubstring(\"Affinity: &corev1.Affinity{\"),\n\t\t\t\t\"uncommented Affinity in memcached controller must be preserved by alpha update\")\n\t\t\tExpect(memcachedControllerStr).To(ContainSubstring(\"NodeAffinity: &corev1.NodeAffinity{\"))\n\t\t\tExpect(memcachedControllerStr).To(ContainSubstring(`Key: \"kubernetes.io/arch\"`),\n\t\t\t\t\"node affinity MatchExpressions must be preserved\")\n\t\t})\n\t})\n})\n\nfunc modifyMakefileControllerTools(projectDir, newVersion string) {\n\tmakefilePath := filepath.Join(projectDir, \"Makefile\")\n\toldLine := \"CONTROLLER_TOOLS_VERSION ?= v0.17.2\"\n\tnewLine := fmt.Sprintf(\"CONTROLLER_TOOLS_VERSION ?= %s\", newVersion)\n\n\tBy(\"replacing the controller-tools version in the Makefile\")\n\tExpect(util.ReplaceInFile(makefilePath, oldLine, newLine)).\n\t\tTo(Succeed(), \"Failed to update CONTROLLER_TOOLS_VERSION in Makefile\")\n\n\tBy(\"committing the Makefile change to simulate user customization\")\n\tcmds := [][]string{\n\t\t{\"git\", \"add\", \"Makefile\"},\n\t\t{\"git\", \"commit\", \"-m\", fmt.Sprintf(\"User modified CONTROLLER_TOOLS_VERSION to %s\", newVersion)},\n\t}\n\tfor _, args := range cmds {\n\t\tcmd := exec.Command(args[0], args[1:]...)\n\t\tcmd.Dir = projectDir\n\t\toutput, err := cmd.CombinedOutput()\n\t\tExpect(err).NotTo(HaveOccurred(), fmt.Sprintf(\"Git command failed: %s\", output))\n\t}\n}\n\nfunc validateMakefileContent(projectDir string) {\n\tmakefilePath := filepath.Join(projectDir, \"Makefile\")\n\tcontent, err := os.ReadFile(makefilePath)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to read Makefile\")\n\n\tmakefile := string(content)\n\n\tExpect(makefile).To(ContainSubstring(`CONTROLLER_TOOLS_VERSION ?= v0.18.0`))\n\tExpect(makefile).To(ContainSubstring(`GOLANGCI_LINT_VERSION ?= v2.1.0`))\n\n\tExpect(makefile).To(ContainSubstring(`.PHONY: test-e2e`))\n\tExpect(makefile).To(ContainSubstring(`go test ./test/e2e/ -v -ginkgo.v`))\n\n\tExpect(makefile).To(ContainSubstring(`.PHONY: cleanup-test-e2e`))\n\tExpect(makefile).To(ContainSubstring(`delete cluster --name $(KIND_CLUSTER)`))\n}\n\n// 4.6.0 and 4.7.0 updates include common changes that should be validated\nfunc validateCommonGoModule(projectDir string) {\n\texpectModuleVersion(projectDir, \"github.com/onsi/ginkgo/v2\", \"v2.22.0\")\n\texpectModuleVersion(projectDir, \"github.com/onsi/gomega\", \"v1.36.1\")\n\texpectModuleVersion(projectDir, \"k8s.io/apimachinery\", \"v0.33.0\")\n\texpectModuleVersion(projectDir, \"k8s.io/client-go\", \"v0.33.0\")\n\texpectModuleVersion(projectDir, \"sigs.k8s.io/controller-runtime\", \"\")\n}\n\nfunc downloadKubebuilderVersion(version string) (string, error) {\n\tbinaryDir, err := os.MkdirTemp(\"\", \"kubebuilder-\"+version+\"-\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create binary directory: %w\", err)\n\t}\n\n\turl := fmt.Sprintf(\n\t\t\"https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s\",\n\t\tversion,\n\t\truntime.GOOS,\n\t\truntime.GOARCH,\n\t)\n\tbinaryPath := filepath.Join(binaryDir, \"kubebuilder\")\n\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to download kubebuilder %s: %w\", version, err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to download kubebuilder %s: HTTP %d\", version, resp.StatusCode)\n\t}\n\n\tfile, err := os.Create(binaryPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create binary file: %w\", err)\n\t}\n\tdefer func() { _ = file.Close() }()\n\n\t_, err = io.Copy(file, resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to write binary: %w\", err)\n\t}\n\n\terr = os.Chmod(binaryPath, 0o755)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to make binary executable: %w\", err)\n\t}\n\n\treturn binaryPath, nil\n}\n\nfunc updateController(projectDir string) {\n\tcontrollerFile := filepath.Join(projectDir, \"internal\", \"controller\", \"testoperator_controller.go\")\n\tExpect(util.ReplaceInFile(controllerFile, \"_ = logf.FromContext(ctx)\", \"log := logf.FromContext(ctx)\")).To(Succeed())\n\tExpect(util.ReplaceInFile(controllerFile, \"// TODO(user): your logic here\", controllerImplementation)).To(Succeed())\n}\n\nfunc updateAPI(projectDir string) {\n\ttypesFile := filepath.Join(projectDir, \"api\", \"v1\", \"testoperator_types.go\")\n\terr := util.ReplaceInFile(typesFile, \"Foo string `json:\\\"foo,omitempty\\\"`\", customField)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to update testoperator_types.go\")\n}\n\nfunc initializeGitRepo(projectDir string) {\n\tcommands := [][]string{\n\t\t{\"git\", \"init\"},\n\t\t{\"git\", \"config\", \"user.email\", \"test@example.com\"},\n\t\t{\"git\", \"config\", \"user.name\", \"Test User\"},\n\t\t{\"git\", \"add\", \"-A\"},\n\t\t{\"git\", \"commit\", \"-m\", \"Initial project with custom code\"},\n\t\t{\"git\", \"branch\", \"-M\", \"main\"},\n\t}\n\tfor _, args := range commands {\n\t\tcmd := exec.Command(args[0], args[1:]...)\n\t\tcmd.Dir = projectDir\n\t\t_, err := cmd.CombinedOutput()\n\t\tif err != nil && strings.Contains(err.Error(), \"already exists\") {\n\t\t\tExpect(exec.Command(\"git\", \"checkout\", \"main\").Run()).To(Succeed())\n\t\t} else {\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t}\n\t}\n}\n\nfunc validateCustomCodePreservation(projectDir string) {\n\tapiFile := filepath.Join(projectDir, \"api\", \"v1\", \"testoperator_types.go\")\n\tcontrollerFile := filepath.Join(projectDir, \"internal\", \"controller\", \"testoperator_controller.go\")\n\n\tapiContent, err := os.ReadFile(apiFile)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(string(apiContent)).To(ContainSubstring(\"Size int32 `json:\\\"size,omitempty\\\"`\"))\n\tExpect(string(apiContent)).To(ContainSubstring(\"// +kubebuilder:validation:Minimum=0\"))\n\tExpect(string(apiContent)).To(ContainSubstring(\"// +kubebuilder:validation:Maximum=3\"))\n\tExpect(string(apiContent)).To(ContainSubstring(\"// +kubebuilder:default=1\"))\n\n\tcontrollerContent, err := os.ReadFile(controllerFile)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(string(controllerContent)).To(ContainSubstring(controllerImplementation))\n}\n\nfunc hasConflictMarkers(projectDir string) bool {\n\thasMarker := false\n\n\terr := filepath.Walk(projectDir, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil || info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tcontent, readErr := os.ReadFile(path)\n\t\tif readErr != nil || bytes.Contains(content, []byte{0}) {\n\t\t\treturn nil // skip unreadable or binary files\n\t\t}\n\n\t\tif strings.Contains(string(content), \"<<<<<<<\") {\n\t\t\thasMarker = true\n\t\t\treturn fmt.Errorf(\"conflict marker found in %s\", path) // short-circuit early\n\t\t}\n\t\treturn nil\n\t})\n\n\tif err != nil && hasMarker {\n\t\treturn true\n\t}\n\treturn hasMarker\n}\n\nfunc validateConflictState(projectDir string) {\n\tBy(\"validating merge stopped with conflicts requiring manual resolution\")\n\n\t// 1. Check file contents for conflict markers\n\tExpect(hasConflictMarkers(projectDir)).To(BeTrue())\n\n\t// 2. Check Git status for conflict-tracked files (UU = both modified)\n\tcmd := exec.Command(\"git\", \"status\", \"--porcelain\")\n\tcmd.Dir = projectDir\n\toutput, err := cmd.CombinedOutput()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tlines := strings.Split(string(output), \"\\n\")\n\tconflictFound := false\n\tfor _, line := range lines {\n\t\tif strings.HasPrefix(line, \"UU \") || strings.HasPrefix(line, \"AA \") {\n\t\t\tconflictFound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tExpect(conflictFound).To(BeTrue(), \"Expected Git to report conflict state in files\")\n}\n\nfunc expectModuleVersion(projectDir, module, version string) {\n\tgoModPath := filepath.Join(projectDir, \"go.mod\")\n\tcontent, err := os.ReadFile(goModPath)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to read go.mod\")\n\n\texpected := fmt.Sprintf(\"%s %s\", module, version)\n\tExpect(string(content)).To(ContainSubstring(expected),\n\t\tfmt.Sprintf(\"Expected to find: %s\", expected))\n}\n\n// expectFileExists asserts that the given path under projectDir exists (file or dir).\nfunc expectFileExists(projectDir, relPath string) {\n\tp := filepath.Join(projectDir, relPath)\n\t_, err := os.Stat(p)\n\tExpect(err).NotTo(HaveOccurred(), \"expected file or dir to exist: %s\", p)\n}\n\n// expectNoConflictMarkersInPath asserts that no file under dir (relative to projectDir) contains Git conflict markers.\n// Skips if dir does not exist (e.g. optional plugin not scaffolded).\nfunc expectNoConflictMarkersInPath(projectDir, dir string) {\n\troot := filepath.Join(projectDir, dir)\n\tif _, err := os.Stat(root); os.IsNotExist(err) {\n\t\treturn\n\t}\n\t_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil || info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tcontent, readErr := os.ReadFile(path)\n\t\tif readErr != nil || bytes.Contains(content, []byte{0}) {\n\t\t\treturn nil\n\t\t}\n\t\tExpect(string(content)).NotTo(ContainSubstring(\"<<<<<<<\"),\n\t\t\t\"expected no conflict markers in plugin output: %s\", path)\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/prepare.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/common\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n)\n\nconst defaultBranch = \"main\"\n\ntype releaseResponse struct {\n\tTagName string `json:\"tag_name\"`\n}\n\n// Prepare resolves version and binary URL details after validation.\n// Should be called after Validate().\nfunc (opts *Update) Prepare() error {\n\tif opts.FromBranch == \"\" {\n\t\t// TODO: Check if is possible to use get to determine the default branch\n\t\tlog.Warn(\"No --from-branch specified, using 'main' as default\")\n\t\topts.FromBranch = defaultBranch\n\t}\n\n\tpath, err := common.GetInputPath(\"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to determine project path: %w\", err)\n\t}\n\tconfig, err := common.LoadProjectConfig(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load PROJECT config: %w\", err)\n\t}\n\topts.FromVersion, err = opts.defineFromVersion(config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to determine the version to use for the upgrade from: %w\", err)\n\t}\n\topts.ToVersion = opts.defineToVersion()\n\treturn nil\n}\n\n// defineFromVersion will return the CLI version to be used for the update with the v prefix.\nfunc (opts *Update) defineFromVersion(config store.Store) (string, error) {\n\tfromVersion := opts.FromVersion\n\n\tif len(fromVersion) == 0 {\n\t\tfromVersion = config.Config().GetCliVersion()\n\t}\n\n\tif len(fromVersion) == 0 {\n\t\treturn \"\", fmt.Errorf(\"no version specified in PROJECT file. \" +\n\t\t\t\"Please use --from-version flag to specify the version to update from\")\n\t}\n\n\tif !strings.HasPrefix(fromVersion, \"v\") {\n\t\tfromVersion = \"v\" + fromVersion\n\t}\n\n\treturn fromVersion, nil\n}\n\nfunc (opts *Update) defineToVersion() string {\n\tif len(opts.ToVersion) != 0 {\n\t\tif !strings.HasPrefix(opts.ToVersion, \"v\") {\n\t\t\treturn \"v\" + opts.ToVersion\n\t\t}\n\t\treturn opts.ToVersion\n\t}\n\n\topts.ToVersion, _ = fetchLatestRelease()\n\n\treturn opts.ToVersion\n}\n\nfunc fetchLatestRelease() (string, error) {\n\tresp, err := http.Get(\"https://api.github.com/repos/kubernetes-sigs/kubebuilder/releases/latest\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to fetch latest release: %w\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tlog.Info(\"failed to close connection\", \"error\", err)\n\t\t}\n\t}()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\tvar release releaseResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&release); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse response: %w\", err)\n\t}\n\n\treturn release.TagName, nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/prepare_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/h2non/gock\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/common\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\tv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nconst (\n\ttestFromVersion = \"v4.5.0\"\n\ttestToVersion   = \"v4.6.0\"\n)\n\nvar _ = Describe(\"Prepare for internal update\", func() {\n\tvar (\n\t\ttmpDir      string\n\t\tworkDir     string\n\t\tprojectFile string\n\t\tmockGh      string\n\t\terr         error\n\n\t\tlogFile string\n\t\toldPath string\n\t\topts    Update\n\t)\n\n\tBeforeEach(func() {\n\t\tworkDir, err = os.Getwd()\n\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t// 1) Create tmp dir and chdir first\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"kubebuilder-prepare-test\")\n\t\tExpect(err).ToNot(HaveOccurred())\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t// 2) Now that tmpDir exists, set logFile and PATH\n\t\tlogFile = filepath.Join(tmpDir, \"bin.log\")\n\n\t\toldPath = os.Getenv(\"PATH\")\n\t\tExpect(os.Setenv(\"PATH\", tmpDir+string(os.PathListSeparator)+oldPath)).To(Succeed())\n\n\t\tprojectFile = filepath.Join(tmpDir, yaml.DefaultPath)\n\n\t\tconfig.Register(config.Version{Number: 3}, func() config.Config {\n\t\t\treturn &v3.Cfg{Version: config.Version{Number: 3}, CliVersion: \"v1.0.0\"}\n\t\t})\n\n\t\tgock.New(\"https://api.github.com\").\n\t\t\tGet(\"/repos/kubernetes-sigs/kubebuilder/releases/latest\").\n\t\t\tReply(200).\n\t\t\tJSON(map[string]string{\"tag_name\": \"v1.1.0\"})\n\n\t\t// 3) Create the mock gh inside tmpDir (on PATH)\n\t\tmockGh = filepath.Join(tmpDir, \"gh\")\n\t\tghOK := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"repo\" && \"$2\" == \"view\" ]]; then\n  echo \"acme/repo\"\n  exit 0\nfi\nif [[ \"$1\" == \"issue\" && \"$2\" == \"create\" ]]; then\n  exit 0\nfi\nexit 0`\n\t\tExpect(mockBinResponse(ghOK, mockGh)).To(Succeed())\n\t})\n\n\tAfterEach(func() {\n\t\tExpect(os.Setenv(\"PATH\", oldPath)).To(Succeed())\n\t\terr = os.Chdir(workDir)\n\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\terr = os.RemoveAll(tmpDir)\n\t\tExpect(err).ToNot(HaveOccurred())\n\t\tdefer gock.Off()\n\t})\n\n\tContext(\"Prepare\", func() {\n\t\tDescribeTable(\"should succeed for valid options\",\n\t\t\tfunc(options *Update) {\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\tresult := options.Prepare()\n\t\t\t\tExpect(result).ToNot(HaveOccurred())\n\t\t\t\tExpect(options.Prepare()).To(Succeed())\n\t\t\t\tExpect(options.FromVersion).To(Equal(\"v1.0.0\"))\n\t\t\t\tExpect(options.ToVersion).To(Equal(\"v1.1.0\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{FromVersion: \"v1.0.0\", ToVersion: \"v1.1.0\", FromBranch: \"test\"}),\n\t\t\tEntry(\"options\", &Update{FromVersion: \"1.0.0\", ToVersion: \"1.1.0\", FromBranch: \"test\"}),\n\t\t\tEntry(\"options\", &Update{FromVersion: \"v1.0.0\", ToVersion: \"v1.1.0\"}),\n\t\t\tEntry(\"options\", &Update{}),\n\t\t)\n\n\t\tDescribeTable(\"Should fail to prepare if project path is undetermined\",\n\t\t\tfunc(options *Update) {\n\t\t\t\terr = options.Prepare()\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).Should(ContainSubstring(\"failed to determine project path\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{FromVersion: \"v1.0.0\", ToVersion: \"v1.1.0\", FromBranch: \"test\"}),\n\t\t)\n\n\t\tDescribeTable(\"Should fail if PROJECT config could not be loaded\",\n\t\t\tfunc(options *Update) {\n\t\t\t\tconst version = \"\"\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\terr = options.Prepare()\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).Should(ContainSubstring(\"failed to load PROJECT config\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{FromVersion: \"v1.0.0\", ToVersion: \"v1.1.0\", FromBranch: \"test\"}),\n\t\t)\n\n\t\tDescribeTable(\"Should fail if FromVersion cannot be determined\",\n\t\t\tfunc(options *Update) {\n\t\t\t\tconfig.Register(config.Version{Number: 3}, func() config.Config {\n\t\t\t\t\treturn &v3.Cfg{Version: config.Version{Number: 3}}\n\t\t\t\t})\n\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\t\t\t\tExpect(options.FromVersion).To(BeEquivalentTo(\"\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{}),\n\t\t)\n\t})\n\n\tContext(\"DefineFromVersion\", func() {\n\t\tDescribeTable(\"Should succeed when --from-version or CliVersion in Project config is present\",\n\t\t\tfunc(options *Update) {\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\tconfig, errLoad := common.LoadProjectConfig(tmpDir)\n\t\t\t\tExpect(errLoad).ToNot(HaveOccurred())\n\t\t\t\tfromVersion, errLoad := options.defineFromVersion(config)\n\t\t\t\tExpect(errLoad).ToNot(HaveOccurred())\n\t\t\t\tExpect(fromVersion).To(BeEquivalentTo(\"v1.0.0\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{FromVersion: \"\"}),\n\t\t\tEntry(\"options\", &Update{FromVersion: \"1.0.0\"}),\n\t\t)\n\t\tDescribeTable(\"Should fail when --from-version and CliVersion in Project config both are absent\",\n\t\t\tfunc(options *Update) {\n\t\t\t\tconfig.Register(config.Version{Number: 3}, func() config.Config {\n\t\t\t\t\treturn &v3.Cfg{Version: config.Version{Number: 3}}\n\t\t\t\t})\n\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\tconfig, errLoad := common.LoadProjectConfig(tmpDir)\n\t\t\t\tExpect(errLoad).NotTo(HaveOccurred())\n\t\t\t\tfromVersion, errLoad := options.defineFromVersion(config)\n\t\t\t\tExpect(errLoad).To(HaveOccurred())\n\t\t\t\tExpect(errLoad.Error()).To(ContainSubstring(\"no version specified in PROJECT file\"))\n\t\t\t\tExpect(fromVersion).To(Equal(\"\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{FromVersion: \"\"}),\n\t\t)\n\t})\n\n\tContext(\"DefineToVersion\", func() {\n\t\tDescribeTable(\"Should succeed.\",\n\t\t\tfunc(options *Update) {\n\t\t\t\ttoVersion := options.defineToVersion()\n\t\t\t\tExpect(toVersion).To(BeEquivalentTo(\"v1.1.0\"))\n\t\t\t},\n\t\t\tEntry(\"options\", &Update{ToVersion: \"1.1.0\"}),\n\t\t\tEntry(\"options\", &Update{ToVersion: \"v1.1.0\"}),\n\t\t\tEntry(\"options\", &Update{}),\n\t\t)\n\t})\n\n\tContext(\"OpenGitHubIssue\", func() {\n\t\tIt(\"creates issue without conflicts\", func() {\n\t\t\topts.FromBranch = defaultBranch\n\t\t\topts.FromVersion = \"v4.5.1\"\n\t\t\topts.ToVersion = \"v4.8.0\"\n\n\t\t\terr = opts.openGitHubIssue(false)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\ts := string(logs)\n\n\t\t\tExpect(s).To(ContainSubstring(\"repo view --json nameWithOwner --jq .nameWithOwner\"))\n\t\t\tExpect(s).To(ContainSubstring(\"issue create\"))\n\n\t\t\texpURL := fmt.Sprintf(\"https://github.com/%s/compare/%s...%s?expand=1\",\n\t\t\t\t\"acme/repo\", opts.FromBranch, opts.getOutputBranchName())\n\t\t\tExpect(s).To(ContainSubstring(expURL))\n\t\t\tExpect(s).To(ContainSubstring(opts.ToVersion))\n\t\t\tExpect(s).To(ContainSubstring(opts.FromVersion))\n\t\t})\n\n\t\tIt(\"creates issue with conflicts template\", func() {\n\t\t\topts.FromBranch = defaultBranch\n\t\t\topts.FromVersion = \"v4.5.2\"\n\t\t\topts.ToVersion = \"v4.10.0\"\n\n\t\t\terr = opts.openGitHubIssue(true)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, _ := os.ReadFile(logFile)\n\t\t\ts := string(logs)\n\t\t\tExpect(s).To(ContainSubstring(\"Resolve conflicts\"))\n\t\t\tExpect(s).To(ContainSubstring(\"make manifests generate fmt vet lint-fix\"))\n\t\t})\n\n\t\tIt(\"fails when repo detection fails\", func() {\n\t\t\tfailRepo := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"repo\" && \"$2\" == \"view\" ]]; then\n  exit 1\nfi\nexit 0`\n\t\t\tExpect(mockBinResponse(failRepo, mockGh)).To(Succeed())\n\n\t\t\terr = opts.openGitHubIssue(false)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to detect GitHub repository\"))\n\t\t})\n\n\t\tIt(\"fails when issue creation fails\", func() {\n\t\t\tfailIssue := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"repo\" && \"$2\" == \"view\" ]]; then\n  echo \"acme/repo\"\n  exit 0\nfi\nif [[ \"$1\" == \"issue\" && \"$2\" == \"create\" ]]; then\n  exit 1\nfi\nexit 0`\n\t\t\tExpect(mockBinResponse(failIssue, mockGh)).To(Succeed())\n\n\t\t\topts.FromBranch = defaultBranch\n\t\t\topts.FromVersion = testFromVersion\n\t\t\topts.ToVersion = testToVersion\n\n\t\t\terr = opts.openGitHubIssue(false)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to create GitHub issue: exit status 1\"))\n\t\t})\n\t})\n\n\tContext(\"Version Handling Edge Cases\", func() {\n\t\tDescribeTable(\"Should handle version prefix normalization in defineToVersion\",\n\t\t\tfunc(inputVersion, expectedVersion string) {\n\t\t\t\topts := &Update{ToVersion: inputVersion}\n\t\t\t\tnormalizedVersion := opts.defineToVersion()\n\t\t\t\tExpect(normalizedVersion).To(Equal(expectedVersion))\n\t\t\t},\n\t\t\tEntry(\"adds v prefix when missing\", \"1.0.0\", \"v1.0.0\"),\n\t\t\tEntry(\"keeps v prefix when present\", \"v1.0.0\", \"v1.0.0\"),\n\t\t\tEntry(\"handles semantic versioning\", \"1.2.3\", \"v1.2.3\"),\n\t\t\tEntry(\"handles pre-release versions\", \"1.0.0-alpha\", \"v1.0.0-alpha\"),\n\t\t\tEntry(\"handles build metadata\", \"1.0.0+build.1\", \"v1.0.0+build.1\"),\n\t\t)\n\n\t\tDescribeTable(\"Should handle malformed versions gracefully during validation\",\n\t\t\tfunc(invalidFromVersion, invalidToVersion string) {\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\topts := &Update{FromVersion: invalidFromVersion, ToVersion: invalidToVersion, FromBranch: \"test\"}\n\t\t\t\terr = opts.Prepare()\n\t\t\t\t// Should handle gracefully or provide clear error message\n\t\t\t\tif err != nil {\n\t\t\t\t\tExpect(err.Error()).To(Or(\n\t\t\t\t\t\tContainSubstring(\"version\"),\n\t\t\t\t\t\tContainSubstring(\"validate\"),\n\t\t\t\t\t\tContainSubstring(\"semantic\"),\n\t\t\t\t\t))\n\t\t\t\t}\n\t\t\t},\n\t\t\tEntry(\"invalid from version\", \"not.a.version\", \"v1.0.0\"),\n\t\t\tEntry(\"invalid to version\", \"v1.0.0\", \"not.a.version\"),\n\t\t\tEntry(\"special characters in from\", \"v1.0.0$invalid\", \"v1.0.0\"),\n\t\t\tEntry(\"special characters in to\", \"v1.0.0\", \"v1.0.0$invalid\"),\n\t\t)\n\t})\n\n\tContext(\"GitHub Integration Edge Cases\", func() {\n\t\tBeforeEach(func() {\n\t\t\topts.FromBranch = defaultBranch\n\t\t\topts.FromVersion = testFromVersion\n\t\t\topts.ToVersion = testToVersion\n\t\t})\n\n\t\tIt(\"handles missing gh CLI\", func() {\n\t\t\tnoGh := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"repo\" && \"$2\" == \"view\" ]]; then\n  echo \"command not found: gh\" >&2\n  exit 127\nfi\nexit 0`\n\t\t\tExpect(mockBinResponse(noGh, mockGh)).To(Succeed())\n\n\t\t\terr = opts.openGitHubIssue(false)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to detect GitHub repository\"))\n\t\t})\n\n\t\tIt(\"handles gh CLI authentication failure\", func() {\n\t\t\tauthFailGh := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"repo\" && \"$2\" == \"view\" ]]; then\n  echo \"error: authentication required\" >&2\n  exit 1\nfi\nexit 0`\n\t\t\tExpect(mockBinResponse(authFailGh, mockGh)).To(Succeed())\n\n\t\t\terr = opts.openGitHubIssue(false)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to detect GitHub repository\"))\n\t\t})\n\t})\n\n\tContext(\"Output Branch Name Generation\", func() {\n\t\tDescribeTable(\"Should generate correct output branch names\",\n\t\t\tfunc(fromVersion, toVersion, expectedSuffix string) {\n\t\t\t\topts := &Update{\n\t\t\t\t\tFromVersion: fromVersion,\n\t\t\t\t\tToVersion:   toVersion,\n\t\t\t\t}\n\t\t\t\tbranchName := opts.getOutputBranchName()\n\t\t\t\tExpect(branchName).To(ContainSubstring(\"kubebuilder-update-from\"))\n\t\t\t\tExpect(branchName).To(ContainSubstring(expectedSuffix))\n\t\t\t},\n\t\t\tEntry(\"standard versions\", \"v1.0.0\", \"v1.1.0\", \"v1.0.0-to-v1.1.0\"),\n\t\t\tEntry(\"versions without v prefix\", \"1.0.0\", \"1.1.0\", \"1.0.0-to-1.1.0\"),\n\t\t\tEntry(\"pre-release versions\", \"v1.0.0-alpha\", \"v1.1.0-beta\", \"v1.0.0-alpha-to-v1.1.0-beta\"),\n\t\t)\n\n\t\tIt(\"uses custom output branch when specified\", func() {\n\t\t\tcustomBranch := \"my-custom-update-branch\"\n\t\t\topts := &Update{\n\t\t\t\tFromVersion:  \"v1.0.0\",\n\t\t\t\tToVersion:    \"v1.1.0\",\n\t\t\t\tOutputBranch: customBranch,\n\t\t\t}\n\t\t\tbranchName := opts.getOutputBranchName()\n\t\t\tExpect(branchName).To(Equal(customBranch))\n\t\t})\n\t})\n\n\tContext(\"Git Configuration Validation\", func() {\n\t\tIt(\"should handle empty git config\", func() {\n\t\t\topts := &Update{\n\t\t\t\tFromVersion: \"v1.0.0\",\n\t\t\t\tToVersion:   \"v1.1.0\",\n\t\t\t\tFromBranch:  \"test\",\n\t\t\t\tGitConfig:   []string{}, // Empty git config\n\t\t\t}\n\t\t\tconst version = `version: \"3\"`\n\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\terr = opts.Prepare()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should handle invalid git config format\", func() {\n\t\t\topts := &Update{\n\t\t\t\tFromVersion: \"v1.0.0\",\n\t\t\t\tToVersion:   \"v1.1.0\",\n\t\t\t\tFromBranch:  \"test\",\n\t\t\t\tGitConfig:   []string{\"invalid-config-format\"}, // Invalid format\n\t\t\t}\n\t\t\tconst version = `version: \"3\"`\n\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\terr = opts.Prepare()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\t})\n\n\tContext(\"Branch Name Validation\", func() {\n\t\tDescribeTable(\"Should handle various branch name formats\",\n\t\t\tfunc(branchName string, shouldSucceed bool) {\n\t\t\t\topts := &Update{\n\t\t\t\t\tFromVersion: \"v1.0.0\",\n\t\t\t\t\tToVersion:   \"v1.1.0\",\n\t\t\t\t\tFromBranch:  branchName,\n\t\t\t\t}\n\t\t\t\tconst version = `version: \"3\"`\n\t\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\t\terr = opts.Prepare()\n\t\t\t\tif shouldSucceed {\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t} else {\n\t\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\t}\n\t\t\t},\n\t\t\tEntry(\"standard main branch\", \"main\", true),\n\t\t\tEntry(\"standard master branch\", \"master\", true),\n\t\t\tEntry(\"feature branch\", \"feature/my-feature\", true),\n\t\t\tEntry(\"release branch\", \"release/v1.0.0\", true),\n\t\t\tEntry(\"branch with numbers\", \"branch-123\", true),\n\t\t\tEntry(\"empty branch name\", \"\", true),\n\t\t)\n\t})\n\n\tContext(\"Resource Cleanup and Error Recovery\", func() {\n\t\tIt(\"should handle cleanup when preparation fails\", func() {\n\t\t\t// This test ensures that temporary resources are cleaned up even when operations fail\n\t\t\tfailGit := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tmockGit := filepath.Join(tmpDir, \"git\")\n\t\t\tExpect(mockBinResponse(failGit, mockGit)).To(Succeed())\n\n\t\t\topts := &Update{\n\t\t\t\tFromVersion: \"v1.0.0\",\n\t\t\t\tToVersion:   \"v1.1.0\",\n\t\t\t\tFromBranch:  \"test\",\n\t\t\t}\n\t\t\tconst version = `version: \"3\"`\n\t\t\tExpect(os.WriteFile(projectFile, []byte(version), 0o644)).To(Succeed())\n\n\t\t\terr = opts.Prepare()\n\t\t\t// The specific error depends on when git fails in the preparation process\n\t\t\t// This test ensures the system handles git failures gracefully\n\t\t\tif err != nil {\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"git\"))\n\t\t\t}\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestCommand(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"alpha command: update suite\")\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/update.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/update/helpers\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n)\n\n// Update contains configuration for the update operation.\ntype Update struct {\n\t// FromVersion is the release version to update FROM (the base/original scaffold),\n\t// e.g., \"v4.5.0\". This is used to regenerate the ancestor scaffold.\n\tFromVersion string\n\n\t// ToVersion is the release version to update TO (the target scaffold),\n\t// e.g., \"v4.6.0\". This is used to regenerate the upgrade scaffold.\n\tToVersion string\n\n\t// FromBranch is the base Git branch that represents the user's current project state,\n\t// e.g., \"main\". Its contents are captured into the \"original\" branch during the update.\n\tFromBranch string\n\n\t// Force, when true, commits the merge result even if there are conflicts.\n\t// In that case, conflict markers are kept in the files.\n\tForce bool\n\n\t// ShowCommits controls whether to keep full history (no squash).\n\t//   - true  => keep history: point the output branch at the merge commit\n\t//              (no squashed commit is created).\n\t//   - false => squash: write the merge tree as a single commit on the output branch.\n\t//\n\t// The output branch name defaults to \"kubebuilder-update-from-<FromVersion>-to-<ToVersion>\"\n\t// unless OutputBranch is explicitly set.\n\tShowCommits bool\n\n\t// RestorePath is a list of paths to restore from the base branch (FromBranch)\n\t// when SQUASHING, so things like CI config remain unchanged.\n\t// Example: []string{\".github/workflows\"}\n\t// NOTE: This is ignored when ShowCommits == true.\n\tRestorePath []string\n\n\t// OutputBranch is the name of the branch that will receive the result:\n\t//   - In squash mode (ShowCommits == false): the single squashed commit.\n\t//   - In keep-history mode (ShowCommits == true): the merge commit.\n\t// If empty, it defaults to \"kubebuilder-update-from-<FromVersion>-to-<ToVersion>\".\n\tOutputBranch string\n\n\t// Push, when true, pushes the OutputBranch to the \"origin\" remote after the update completes.\n\tPush bool\n\n\t// CommitMessage is the custom merge message to use for successful merges (no conflicts).\n\t// Set via --merge-message flag.\n\t// If empty, defaults to: \"chore(kubebuilder): update scaffold <from> -> <to>\".\n\tCommitMessage string\n\n\t// CommitMessageConflict is the custom conflict message to use when conflicts occur.\n\t// Set via --conflict-message flag.\n\t// If empty, defaults to: \"chore(kubebuilder): (:warning: manual conflict resolution required)\n\t// update scaffold <from> -> <to>\".\n\tCommitMessageConflict string\n\n\t// OpenGhIssue, when true, automatically creates a GitHub issue after the update\n\t// completes. The issue includes a pre-filled checklist and a compare link from\n\t// the base branch (--from-branch) to the output branch. This requires the GitHub\n\t// CLI (`gh`) to be installed and authenticated in the local environment.\n\tOpenGhIssue bool\n\n\tUseGhModels bool\n\n\t// GitConfig holds per-invocation Git settings applied to every `git` command via\n\t// `git -c key=value`.\n\t//\n\t// Examples:\n\t//   []string{\"merge.renameLimit=999999\"}         // improve rename detection during merges\n\t//   []string{\"diff.renameLimit=999999\"}          // improve rename detection during diffs\n\t//   []string{\"merge.conflictStyle=diff3\"}        // show ancestor in conflict markers\n\t//   []string{\"rerere.enabled=true\"}              // reuse recorded resolutions\n\t//\n\t// Defaults:\n\t//   When no --git-config flags are provided, the updater adds:\n\t//     []string{\"merge.renameLimit=999999\", \"diff.renameLimit=999999\"}\n\t//\n\t// Behavior:\n\t//   • If one or more --git-config flags are supplied, those values are appended on top of the defaults.\n\t//   • To disable the defaults entirely, include a literal \"disable\", for example:\n\t//       --git-config disable --git-config rerere.enabled=true\n\tGitConfig []string\n\n\t// Temporary branches created during the update process. These are internal to the run\n\t// and are surfaced for transparency/debugging:\n\t//   - AncestorBranch: clean scaffold generated from FromVersion\n\t//   - OriginalBranch: snapshot of the user's current project (FromBranch)\n\t//   - UpgradeBranch:  clean scaffold generated from ToVersion\n\t//   - MergeBranch:    result of merging Original into Upgrade (pre-output)\n\tAncestorBranch string\n\tOriginalBranch string\n\tUpgradeBranch  string\n\tMergeBranch    string\n}\n\n// Update a project using a default three-way Git merge.\n// This helps apply new scaffolding changes while preserving custom code.\nfunc (opts *Update) Update() error {\n\t// Inform users about GitHub Models if they're opening an issue but not using AI summary\n\tif opts.OpenGhIssue && !opts.UseGhModels {\n\t\tlog.Info(\"Consider enabling GitHub Models to get an AI summary to help with the update\")\n\t\tlog.Info(\"Use the --use-gh-models flag if your project/organization has permission to use GitHub Models\")\n\t}\n\n\tlog.Info(\"Checking out base branch\", \"branch\", opts.FromBranch)\n\tcheckoutCmd := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.FromBranch)\n\tif err := checkoutCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout base branch %s: %w\", opts.FromBranch, err)\n\t}\n\n\tsuffix := time.Now().Format(\"02-01-06-15-04\")\n\n\topts.AncestorBranch = \"tmp-ancestor-\" + suffix\n\topts.OriginalBranch = \"tmp-original-\" + suffix\n\topts.UpgradeBranch = \"tmp-upgrade-\" + suffix\n\topts.MergeBranch = \"tmp-merge-\" + suffix\n\n\tlog.Debug(\"temporary branches\",\n\t\t\"ancestor\", opts.AncestorBranch,\n\t\t\"original\", opts.OriginalBranch,\n\t\t\"upgrade\", opts.UpgradeBranch,\n\t\t\"merge\", opts.MergeBranch,\n\t)\n\n\t// 1. Creates an ancestor branch based on base branch\n\t// 2. Deletes everything except .git and PROJECT\n\t// 3. Installs old release\n\t// 4. Runs alpha generate with old release binary\n\t// 5. Commits the result\n\tlog.Info(\"Preparing Ancestor branch\", \"branch_name\", opts.AncestorBranch)\n\tif err := opts.prepareAncestorBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to prepare ancestor branch: %w\", err)\n\t}\n\t// 1. Creates original branch\n\t// 2. Ensure that original branch is == Based on user’s current base branch content with\n\t// git checkout \"main\" -- .\n\t// 3. Commits this state\n\tlog.Info(\"Preparing Original branch\", \"branch_name\", opts.OriginalBranch)\n\tif err := opts.prepareOriginalBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout current off ancestor: %w\", err)\n\t}\n\t// 1. Creates upgrade branch from ancestor\n\t// 2. Cleans up the branch by removing all files except .git and PROJECT\n\t// 2. Regenerates scaffold using alpha generate with new version\n\t// 3. Commits the result\n\tlog.Info(\"Preparing Upgrade branch\", \"branch_name\", opts.UpgradeBranch)\n\tif err := opts.prepareUpgradeBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout upgrade off ancestor: %w\", err)\n\t}\n\n\t// 1. Creates merge branch from upgrade\n\t// 2. Merges in original (user code)\n\t// 3. If conflicts occur, it will warn the user and leave the merge branch for manual resolution\n\t// 4. If merge is clean, it stages the changes and commits the result\n\tlog.Info(\"Preparing Merge branch and performing merge\", \"branch_name\", opts.MergeBranch)\n\thasConflicts, err := opts.mergeOriginalToUpgrade()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to merge upgrade into merge branch: %w\", err)\n\t}\n\n\t// Squash or keep commits based on ShowCommits flag\n\tif opts.ShowCommits {\n\t\tlog.Info(\"Keeping commits history\")\n\t\tout := opts.getOutputBranchName()\n\t\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-b\", out, opts.MergeBranch).Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"checkout %s: %w\", out, err)\n\t\t}\n\t} else {\n\t\tlog.Info(\"Squashing merge result to output branch\", \"output_branch\", opts.getOutputBranchName())\n\t\tif err := opts.squashToOutputBranch(hasConflicts); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to squash to output branch: %w\", err)\n\t\t}\n\t}\n\n\t// Push the output branch if requested\n\tif opts.Push {\n\t\tif opts.Push {\n\t\t\tout := opts.getOutputBranchName()\n\t\t\t_ = helpers.GitCmd(opts.GitConfig, \"checkout\", out).Run()\n\t\t\tif err := helpers.GitCmd(opts.GitConfig, \"push\", \"-u\", \"origin\", out).Run(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to push %s: %w\", out, err)\n\t\t\t}\n\t\t}\n\t}\n\n\topts.cleanupTempBranches()\n\tlog.Info(\"Update completed successfully\")\n\n\tif opts.OpenGhIssue {\n\t\tif err := opts.openGitHubIssue(hasConflicts); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open GitHub issue: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (opts *Update) openGitHubIssue(hasConflicts bool) error {\n\tlog.Info(\"Creating GitHub Issue to track the need to update the project\")\n\tout := opts.getOutputBranchName()\n\n\t// Detect repo \"owner/name\"\n\trepoCmd := exec.Command(\"gh\", \"repo\", \"view\", \"--json\", \"nameWithOwner\", \"--jq\", \".nameWithOwner\")\n\trepoBytes, err := repoCmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to detect GitHub repository via `gh repo view`: %s\", err)\n\t}\n\trepo := strings.TrimSpace(string(repoBytes))\n\n\tcreatePRURL := fmt.Sprintf(\"https://github.com/%s/compare/%s...%s?expand=1\", repo, opts.FromBranch, out)\n\ttitle := fmt.Sprintf(helpers.IssueTitleTmpl, opts.ToVersion, opts.FromVersion)\n\n\t// Skip if an open issue with same title already exists\n\tcheckCmd := exec.Command(\"gh\", \"issue\", \"list\",\n\t\t\"--repo\", repo,\n\t\t\"--state\", \"open\",\n\t\t\"--search\", fmt.Sprintf(\"in:title \\\"%s\\\"\", title),\n\t\t\"--json\", \"title\")\n\tif checkOut, checkErr := checkCmd.Output(); checkErr == nil && strings.Contains(string(checkOut), title) {\n\t\tlog.Info(\"GitHub Issue already exists, skipping creation\", \"title\", title)\n\t\treturn nil\n\t}\n\n\t// Base issue body\n\tvar body string\n\tif hasConflicts {\n\t\tbody = fmt.Sprintf(helpers.IssueBodyTmplWithConflicts, opts.ToVersion, createPRURL, opts.FromVersion, out)\n\t} else {\n\t\tbody = fmt.Sprintf(helpers.IssueBodyTmpl, opts.ToVersion, createPRURL, opts.FromVersion, out)\n\t}\n\n\tlog.Info(\"Creating GitHub Issue\")\n\tcreateCmd := exec.Command(\"gh\", \"issue\", \"create\",\n\t\t\"--repo\", repo,\n\t\t\"--title\", title,\n\t\t\"--body\", body,\n\t)\n\tcreateOut, createErr := createCmd.CombinedOutput()\n\tif createErr != nil {\n\t\treturn fmt.Errorf(\"failed to create GitHub issue: %v\\n%s\", createErr, string(createOut))\n\t}\n\toutStr := string(createOut)\n\n\t// Try to extract the issue URL from stdout\n\tissueURL := helpers.FirstURL(outStr)\n\n\t// Fallback: query the just-created issue by title\n\tif issueURL == \"\" {\n\t\tviewCmd := exec.Command(\"gh\", \"issue\", \"list\",\n\t\t\t\"--repo\", repo,\n\t\t\t\"--state\", \"open\",\n\t\t\t\"--search\", fmt.Sprintf(\"in:title \\\"%s\\\"\", title),\n\t\t\t\"--json\", \"url\",\n\t\t\t\"--jq\", \".[0].url\",\n\t\t)\n\t\turlBytes, vErr := viewCmd.Output()\n\t\tif vErr != nil {\n\t\t\tlog.Warn(\"could not determine issue URL from gh output\", \"stdout\", outStr, \"error\", vErr)\n\t\t}\n\t\tissueURL = strings.TrimSpace(string(urlBytes))\n\t}\n\tlog.Info(\"GitHub Issue created to track the update\", \"url\", issueURL, \"compare\", createPRURL)\n\n\tif opts.UseGhModels {\n\t\tlog.Info(\"Generating AI summary with gh models\")\n\n\t\tif issueURL == \"\" {\n\t\t\treturn fmt.Errorf(\"issue created but URL could not be determined\")\n\t\t}\n\n\t\treleaseURL := fmt.Sprintf(\"https://github.com/kubernetes-sigs/kubebuilder/releases/tag/%s\",\n\t\t\topts.ToVersion)\n\n\t\tctx := helpers.BuildFullPrompet(\n\t\t\topts.FromVersion, opts.ToVersion, opts.FromBranch, out,\n\t\t\tcreatePRURL, releaseURL)\n\n\t\tvar outBuf, errBuf bytes.Buffer\n\t\tcmd := exec.Command(\n\t\t\t\"gh\", \"models\", \"run\", \"openai/gpt-5\",\n\t\t\t\"--system-prompt\", helpers.AiPRPrompt,\n\t\t)\n\t\tcmd.Stdin = strings.NewReader(ctx)\n\t\tcmd.Stdout = &outBuf\n\t\tcmd.Stderr = &errBuf\n\t\tif err := cmd.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"gh models run failed: %w\\nstderr:\\n%s\", err, errBuf.String())\n\t\t}\n\n\t\tsummary := strings.TrimSpace(outBuf.String())\n\t\tif summary != \"\" {\n\t\t\tnum := helpers.IssueNumberFromURL(issueURL)\n\t\t\ttarget := issueURL\n\t\t\targs := make([]string, 4, 7)\n\t\t\targs[0] = \"issue\"\n\t\t\targs[1] = \"comment\"\n\t\t\targs[2] = \"--repo\"\n\t\t\targs[3] = repo\n\t\t\tif num != \"\" {\n\t\t\t\ttarget = num\n\t\t\t}\n\t\t\targs = append(args, target, \"--body\", summary)\n\t\t\tcommentCmd := exec.Command(\"gh\", args...)\n\t\t\tcommentCmd.Stdout = os.Stdout\n\t\t\tcommentCmd.Stderr = os.Stderr\n\t\t\tif err := commentCmd.Run(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to add AI summary comment: %s\", err)\n\t\t\t}\n\t\t\tlog.Info(\"AI summary comment added to the issue\")\n\t\t} else {\n\t\t\tlog.Warn(\"AI summary was empty, no comment added\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (opts *Update) cleanupTempBranches() {\n\t_ = helpers.GitCmd(opts.GitConfig, \"checkout\", opts.getOutputBranchName()).Run()\n\n\tbranches := []string{\n\t\topts.AncestorBranch,\n\t\topts.OriginalBranch,\n\t\topts.UpgradeBranch,\n\t\topts.MergeBranch,\n\t}\n\n\tfor _, b := range branches {\n\t\tb = strings.TrimSpace(b)\n\t\tif b == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t// Delete only if it's a LOCAL branch.\n\t\tif err := helpers.GitCmd(opts.GitConfig,\n\t\t\t\"show-ref\", \"--verify\", \"--quiet\", \"refs/heads/\"+b).Run(); err == nil {\n\t\t\t_ = helpers.GitCmd(opts.GitConfig, \"branch\", \"-D\", b).Run()\n\t\t}\n\t}\n}\n\n// getOutputBranchName returns the output branch name\nfunc (opts *Update) getOutputBranchName() string {\n\tif opts.OutputBranch != \"\" {\n\t\treturn opts.OutputBranch\n\t}\n\treturn fmt.Sprintf(\"kubebuilder-update-from-%s-to-%s\", opts.FromVersion, opts.ToVersion)\n}\n\n// preservePaths checks out the paths specified in RestorePath\nfunc (opts *Update) preservePaths() {\n\tfor _, p := range opts.RestorePath {\n\t\tp = strings.TrimSpace(p)\n\t\tif p == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.FromBranch, \"--\", p).Run(); err != nil {\n\t\t\tlog.Warn(\"failed to restore preserved path\", \"path\", p, \"branch\", opts.FromBranch, \"error\", err)\n\t\t}\n\t}\n}\n\n// squashToOutputBranch takes the exact tree of the MergeBranch and writes it as ONE commit\n// on a branch derived from FromBranch (e.g., \"main\"). If RestorePath is set, those paths\n// are restored from the base branch after copying the merge tree, so CI config etc. stays put.\nfunc (opts *Update) squashToOutputBranch(hasConflicts bool) error {\n\tout := opts.getOutputBranchName()\n\n\t// 1) base -> out\n\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.FromBranch).Run(); err != nil {\n\t\treturn fmt.Errorf(\"checkout %s: %w\", opts.FromBranch, err)\n\t}\n\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-B\", out, opts.FromBranch).Run(); err != nil {\n\t\treturn fmt.Errorf(\"create/reset %s from %s: %w\", out, opts.FromBranch, err)\n\t}\n\n\t// 2) clean worktree, then copy merge tree\n\tif err := helpers.CleanWorktree(\"output branch\"); err != nil {\n\t\treturn fmt.Errorf(\"output branch: %w\", err)\n\t}\n\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.MergeBranch, \"--\", \".\").Run(); err != nil {\n\t\treturn fmt.Errorf(\"checkout %s content: %w\", \"merge\", err)\n\t}\n\n\t// 3) optionally restore preserved paths from base (tests assert on 'git restore …')\n\topts.preservePaths()\n\n\t// 4) stage and single squashed commit\n\tif err := helpers.GitCmd(opts.GitConfig, \"add\", \"--all\").Run(); err != nil {\n\t\treturn fmt.Errorf(\"stage output: %w\", err)\n\t}\n\n\tif err := helpers.CommitIgnoreEmpty(opts.getMergeMessage(hasConflicts), \"final\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit final branch: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// regenerateProjectWithVersion downloads the release binary for the specified version,\n// and runs the `alpha generate` command to re-scaffold the project\nfunc regenerateProjectWithVersion(version string) error {\n\ttempDir, err := helpers.DownloadReleaseVersionWith(version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to download release %s binary: %w\", version, err)\n\t}\n\tif err := runAlphaGenerate(tempDir, version); err != nil {\n\t\treturn fmt.Errorf(\"failed to run alpha generate on ancestor branch: %w\", err)\n\t}\n\treturn nil\n}\n\n// prepareAncestorBranch prepares the ancestor branch by checking it out,\n// cleaning up the project files, and regenerating the project with the specified version.\nfunc (opts *Update) prepareAncestorBranch() error {\n\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-b\", opts.AncestorBranch, opts.FromBranch).Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to create %s from %s: %w\", opts.AncestorBranch, opts.FromBranch, err)\n\t}\n\tif err := cleanupBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to cleanup the %s : %w\", opts.AncestorBranch, err)\n\t}\n\tif err := regenerateProjectWithVersion(opts.FromVersion); err != nil {\n\t\treturn fmt.Errorf(\"failed to regenerate project with fromVersion %s: %w\", opts.FromVersion, err)\n\t}\n\tgitCmd := helpers.GitCmd(opts.GitConfig, \"add\", \"--all\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage changes in %s: %w\", opts.AncestorBranch, err)\n\t}\n\tcommitMessage := \"(chore) initial scaffold from release version: \" + opts.FromVersion\n\tif err := helpers.CommitIgnoreEmpty(commitMessage, \"ancestor\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit ancestor branch: %w\", err)\n\t}\n\treturn nil\n}\n\n// cleanupBranch removes all files and folders in the current directory\n// except for the .git directory and the PROJECT file.\n// This is necessary to ensure the ancestor branch starts with a clean slate\n// TODO: Analise if this command is still needed in the future.\n// It is required because the alpha generate command in versions prior to v4.7.0 do not properly\n// handle the removal of files in the ancestor branch.\nfunc cleanupBranch() error {\n\tcmd := exec.Command(\"sh\", \"-c\", \"find . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {} +\")\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to clean up files: %w\", err)\n\t}\n\treturn nil\n}\n\n// runMakeTargets runs the make targets needed to keep the tree consistent.\n// If skipConflicts is true, it avoids running targets that are guaranteed\n// to fail noisily when there are unresolved conflicts.\nfunc runMakeTargets(skipConflicts bool) {\n\tif !skipConflicts {\n\t\tfor _, t := range []string{\"manifests\", \"generate\", \"fmt\", \"vet\", \"lint-fix\"} {\n\t\t\tif err := util.RunCmd(fmt.Sprintf(\"Running make %s\", t), \"make\", t); err != nil {\n\t\t\t\tlog.Warn(\"make target failed\", \"target\", t, \"error\", err)\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\t// Conflict-aware path: decide what to run based on repo state.\n\tcs := helpers.DetectConflicts()\n\ttargets := helpers.DecideMakeTargets(cs)\n\n\tif cs.Makefile {\n\t\tlog.Warn(\"Skipping all make targets because Makefile has merge conflicts\")\n\t\treturn\n\t}\n\tif cs.API {\n\t\tlog.Warn(\"API conflicts detected; skipping make targets: manifests, generate\")\n\t}\n\tif cs.AnyGo {\n\t\tlog.Warn(\"Go conflicts detected; skipping make targets: fmt, vet, lint-fix\")\n\t}\n\n\tif len(targets) == 0 {\n\t\tlog.Warn(\"No make targets will be run due to conflicts\")\n\t\treturn\n\t}\n\n\tfor _, t := range targets {\n\t\tif err := util.RunCmd(fmt.Sprintf(\"Running make %s\", t), \"make\", t); err != nil {\n\t\t\tlog.Warn(\"make target failed\", \"target\", t, \"error\", err)\n\t\t}\n\t}\n}\n\n// runAlphaGenerate executes the old Kubebuilder version's 'alpha generate' command\n// to create clean scaffolding in the ancestor branch. This uses the downloaded\n// binary with the original PROJECT file to recreate the project's initial state.\nfunc runAlphaGenerate(tempDir, version string) error {\n\tlog.Info(\"Generating project\", \"version\", version)\n\n\ttempBinaryPath := tempDir + \"/kubebuilder\"\n\tcmd := exec.Command(tempBinaryPath, \"alpha\", \"generate\")\n\tcmd.Env = envWithPrefixedPath(tempDir)\n\n\t// Capture and reformat subprocess output to match our logging style\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create stdout pipe: %w\", err)\n\t}\n\tstderr, err := cmd.StderrPipe()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create stderr pipe: %w\", err)\n\t}\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"failed to start alpha generate: %w\", err)\n\t}\n\n\t// Forward output while reformatting old-style logs\n\tgo forwardAndReformat(stdout, false)\n\tgo forwardAndReformat(stderr, true)\n\n\tif err := cmd.Wait(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run alpha generate: %w\", err)\n\t}\n\n\tlog.Info(\"Project scaffold generation complete\", \"version\", version)\n\trunMakeTargets(false)\n\treturn nil\n}\n\n// forwardAndReformat reads from a subprocess stream and reformats old-style logging to new style\nfunc forwardAndReformat(reader io.Reader, isStderr bool) {\n\tscanner := bufio.NewScanner(reader)\n\n\t// Regex to match old-style log format: level=info msg=\"message\"\n\tlogPattern := regexp.MustCompile(`^level=(\\w+)\\s+msg=\"?([^\"]*)\"?(.*)$`)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\n\t\t// Check if this line matches the old log format\n\t\tif matches := logPattern.FindStringSubmatch(line); matches != nil {\n\t\t\tlevel := strings.ToUpper(matches[1])\n\t\t\tmessage := matches[2]\n\t\t\trest := matches[3]\n\n\t\t\t// Convert to new format based on level\n\t\t\tswitch level {\n\t\t\tcase \"INFO\":\n\t\t\t\tlog.Info(message + rest)\n\t\t\tcase \"WARN\", \"WARNING\":\n\t\t\t\tlog.Warn(message + rest)\n\t\t\tcase \"ERROR\":\n\t\t\t\tlog.Error(message + rest)\n\t\t\tcase \"DEBUG\":\n\t\t\t\tlog.Debug(message + rest)\n\t\t\tdefault:\n\t\t\t\t// Fallback: print as-is to appropriate stream\n\t\t\t\tif isStderr {\n\t\t\t\t\tfmt.Fprintln(os.Stderr, line)\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Println(line)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Not a log line, print as-is to appropriate stream\n\t\t\tif isStderr {\n\t\t\t\tfmt.Fprintln(os.Stderr, line)\n\t\t\t} else {\n\t\t\t\tfmt.Println(line)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc envWithPrefixedPath(dir string) []string {\n\tenv := os.Environ()\n\tprefix := \"PATH=\"\n\tfor i, kv := range env {\n\t\tif after, ok := strings.CutPrefix(kv, prefix); ok {\n\t\t\tenv[i] = \"PATH=\" + dir + string(os.PathListSeparator) + after\n\t\t\treturn env\n\t\t}\n\t}\n\treturn append(env, \"PATH=\"+dir)\n}\n\n// prepareOriginalBranch creates the 'original' branch from ancestor and\n// populates it with the user's actual project content from the default branch.\n// This represents the current state of the user's project.\nfunc (opts *Update) prepareOriginalBranch() error {\n\tgitCmd := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-b\", opts.OriginalBranch)\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout branch %s: %w\", opts.OriginalBranch, err)\n\t}\n\n\tgitCmd = helpers.GitCmd(opts.GitConfig, \"checkout\", opts.FromBranch, \"--\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout content from %s branch onto %s: %w\", opts.FromBranch, opts.OriginalBranch, err)\n\t}\n\n\tgitCmd = helpers.GitCmd(opts.GitConfig, \"add\", \"--all\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage all changes in current: %w\", err)\n\t}\n\tif err := helpers.CommitIgnoreEmpty(\n\t\tfmt.Sprintf(\"(chore) original code from %s to keep changes\", opts.FromBranch),\n\t\t\"original\",\n\t); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit original branch: %w\", err)\n\t}\n\treturn nil\n}\n\n// prepareUpgradeBranch creates the 'upgrade' branch from ancestor and\n// generates fresh scaffolding using the current (latest) CLI version.\n// This represents what the project should look like with the new version.\nfunc (opts *Update) prepareUpgradeBranch() error {\n\tgitCmd := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-b\", opts.UpgradeBranch, opts.AncestorBranch)\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout %s branch off %s: %w\",\n\t\t\topts.UpgradeBranch, opts.AncestorBranch, err)\n\t}\n\n\tcheckoutCmd := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.UpgradeBranch)\n\tif err := checkoutCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout base branch %s: %w\", opts.UpgradeBranch, err)\n\t}\n\n\tif err := cleanupBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to cleanup the %s branch: %w\", opts.UpgradeBranch, err)\n\t}\n\tif err := regenerateProjectWithVersion(opts.ToVersion); err != nil {\n\t\treturn fmt.Errorf(\"failed to regenerate project with version %s: %w\", opts.ToVersion, err)\n\t}\n\tgitCmd = helpers.GitCmd(opts.GitConfig, \"add\", \"--all\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage changes in %s: %w\", opts.UpgradeBranch, err)\n\t}\n\tif err := helpers.CommitIgnoreEmpty(\n\t\t\"(chore) initial scaffold from release version: \"+opts.ToVersion, \"upgrade\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit upgrade branch: %w\", err)\n\t}\n\treturn nil\n}\n\n// mergeOriginalToUpgrade attempts to merge the upgrade branch\nfunc (opts *Update) mergeOriginalToUpgrade() (bool, error) {\n\thasConflicts := false\n\tif err := helpers.GitCmd(opts.GitConfig, \"checkout\", \"-b\", opts.MergeBranch, opts.UpgradeBranch).Run(); err != nil {\n\t\treturn hasConflicts, fmt.Errorf(\"failed to create merge branch %s from %s: %w\",\n\t\t\topts.MergeBranch, opts.UpgradeBranch, err)\n\t}\n\n\tcheckoutCmd := helpers.GitCmd(opts.GitConfig, \"checkout\", opts.MergeBranch)\n\tif err := checkoutCmd.Run(); err != nil {\n\t\treturn hasConflicts, fmt.Errorf(\"failed to checkout base branch %s: %w\", opts.MergeBranch, err)\n\t}\n\n\tmergeCmd := helpers.GitCmd(opts.GitConfig, \"merge\", \"--no-edit\", \"--no-commit\", opts.OriginalBranch)\n\terr := mergeCmd.Run()\n\tif err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\t// If the merge has an error that is not a conflict, return an error 2\n\t\tif errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {\n\t\t\thasConflicts = true\n\t\t\tif !opts.Force {\n\t\t\t\tlog.Warn(\"Merge stopped due to conflicts. Manual resolution is required.\")\n\t\t\t\tlog.Warn(\"After resolving the conflicts, run the following command:\")\n\t\t\t\tlog.Warn(\"    make manifests generate fmt vet lint-fix\")\n\t\t\t\tlog.Warn(\"This ensures manifests and generated files are up to date, and the project layout remains consistent.\")\n\t\t\t\treturn hasConflicts, fmt.Errorf(\"merge stopped due to conflicts\")\n\t\t\t}\n\t\t\tlog.Warn(\"Merge completed with conflicts. Conflict markers will be committed.\")\n\t\t} else {\n\t\t\treturn hasConflicts, fmt.Errorf(\"merge failed unexpectedly: %w\", err)\n\t\t}\n\t}\n\n\tif !hasConflicts {\n\t\tlog.Info(\"Merge happened without conflicts.\")\n\t}\n\n\t// Best effort to run make targets to ensure the project is in a good state\n\trunMakeTargets(true)\n\n\t// Step 4: Stage and commit\n\tif err := helpers.GitCmd(opts.GitConfig, \"add\", \"--all\").Run(); err != nil {\n\t\treturn hasConflicts, fmt.Errorf(\"failed to stage merge results: %w\", err)\n\t}\n\n\tif err := helpers.CommitIgnoreEmpty(opts.getMergeMessage(hasConflicts), \"merge\"); err != nil {\n\t\treturn hasConflicts, fmt.Errorf(\"failed to commit merge branch: %w\", err)\n\t}\n\tlog.Info(\"Merge completed\")\n\treturn hasConflicts, nil\n}\n\nfunc (opts *Update) getMergeMessage(hasConflicts bool) string {\n\tif hasConflicts {\n\t\t// Use custom conflict message if provided\n\t\tif opts.CommitMessageConflict != \"\" {\n\t\t\treturn opts.CommitMessageConflict\n\t\t}\n\t\t// Otherwise use default conflict format\n\t\treturn helpers.ConflictCommitMessage(opts.FromVersion, opts.ToVersion)\n\t}\n\n\t// Use custom commit message if provided\n\tif opts.CommitMessage != \"\" {\n\t\treturn opts.CommitMessage\n\t}\n\treturn helpers.MergeCommitMessage(opts.FromVersion, opts.ToVersion)\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/update_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/h2non/gock\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/update/helpers\"\n)\n\n// Mock response for binary executables.\nfunc mockBinResponse(script, mockBin string) error {\n\terr := os.WriteFile(mockBin, []byte(script), 0o755)\n\tExpect(err).NotTo(HaveOccurred())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error Mocking bin response: %w\", err)\n\t}\n\treturn nil\n}\n\n// Mock response from an URL.\nfunc mockURLResponse(body, url string, times, reply int) {\n\tparts := strings.Split(url, \"/\")\n\thost := strings.Join(parts[0:3], \"/\")\n\tpath := \"/\" + strings.Join(parts[3:], \"/\")\n\tgock.New(host).Get(path).Times(times).Reply(reply).Body(strings.NewReader(body))\n}\n\nvar _ = Describe(\"Prepare for internal update\", func() {\n\tvar (\n\t\ttmpDir   string\n\t\tmockGit  string\n\t\tmockMake string\n\t\tmocksh   string\n\t\tlogFile  string\n\t\toldPath  string\n\t\terr      error\n\t\topts     Update\n\t)\n\n\tBeforeEach(func() {\n\t\topts = Update{\n\t\t\tFromVersion: \"v4.5.0\",\n\t\t\tToVersion:   \"v4.6.0\",\n\t\t\tFromBranch:  defaultBranch,\n\t\t}\n\n\t\t// Create temporary directory to house fake bin executables.\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"temp-bin\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Common file to log command runs from the fake bin.\n\t\tlogFile = filepath.Join(tmpDir, \"bin.log\")\n\n\t\t// Create fake bin executables.\n\t\tmockGit = filepath.Join(tmpDir, \"git\")\n\t\tmockMake = filepath.Join(tmpDir, \"make\")\n\t\tmocksh = filepath.Join(tmpDir, \"sh\")\n\t\tscript := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 0`\n\t\tExpect(mockBinResponse(script, mockGit)).To(Succeed())\n\t\tExpect(mockBinResponse(script, mockMake)).To(Succeed())\n\t\tExpect(mockBinResponse(script, mocksh)).To(Succeed())\n\n\t\t// Prepend temp bin directory to PATH env.\n\t\toldPath = os.Getenv(\"PATH\")\n\t\tExpect(os.Setenv(\"PATH\", tmpDir+\":\"+oldPath)).To(Succeed())\n\n\t\t// Mock GitHub release download.\n\t\tmockURLResponse(script,\n\t\t\t\"https://github.com/kubernetes-sigs/kubebuilder/releases/download\", 2, 200)\n\t})\n\n\tAfterEach(func() {\n\t\t_ = os.RemoveAll(tmpDir)\n\t\t_ = os.Setenv(\"PATH\", oldPath)\n\t\tdefer gock.Off()\n\t})\n\n\t// Helper that formats the expectations properly.\n\tverifyLogs := func(newBranch, oldBranch, fromVersion string) {\n\t\tlogs, readErr := os.ReadFile(logFile)\n\t\tExpect(readErr).NotTo(HaveOccurred())\n\t\ts := string(logs)\n\n\t\tExpect(s).To(ContainSubstring(\n\t\t\tfmt.Sprintf(\"checkout -b %s %s\", newBranch, oldBranch),\n\t\t))\n\t\tExpect(s).To(ContainSubstring(fmt.Sprintf(\"checkout %s\", newBranch)))\n\t\tExpect(s).To(ContainSubstring(\n\t\t\t\"-c find . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'PROJECT' -exec rm -rf {}\",\n\t\t))\n\t\tExpect(s).To(ContainSubstring(\"alpha generate\"))\n\t\tExpect(s).To(ContainSubstring(\"add --all\"))\n\t\tExpect(s).To(ContainSubstring(\n\t\t\tfmt.Sprintf(\"initial scaffold from release version: %s\", fromVersion),\n\t\t))\n\t}\n\n\tContext(\"Update\", func() {\n\t\tIt(\"succeeds using a default three-way Git merge\", func() {\n\t\t\terr = opts.Update()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout %s\", opts.FromBranch),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"fails when git command fails\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockGit)).To(Succeed())\n\n\t\t\terr = opts.Update()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to checkout base branch %s\", opts.FromBranch),\n\t\t\t))\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout %s\", opts.FromBranch),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"fails when kubebuilder binary cannot be downloaded\", func() {\n\t\t\tgock.Off()\n\t\t\tgock.New(\"https://github.com\").\n\t\t\t\tGet(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\t\tTimes(2).Reply(401).Body(strings.NewReader(\"\"))\n\n\t\t\terr = opts.Update()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to prepare ancestor branch\"))\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout %s\", opts.FromBranch),\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"RegenerateProjectWithVersion\", func() {\n\t\tIt(\"succeeds downloading binary and running `alpha generate`\", func() {\n\t\t\terr = regenerateProjectWithVersion(opts.FromVersion)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"fails downloading binary\", func() {\n\t\t\tgock.Off()\n\t\t\tgock.New(\"https://github.com\").\n\t\t\t\tGet(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\t\tTimes(2).Reply(401).Body(strings.NewReader(\"\"))\n\n\t\t\terr = regenerateProjectWithVersion(opts.FromVersion)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to download release %s binary\", opts.FromVersion),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"fails running alpha generate\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tgock.Off()\n\t\t\tgock.New(\"https://github.com\").\n\t\t\t\tGet(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\t\tTimes(2).Reply(200).Body(strings.NewReader(fail))\n\n\t\t\terr = regenerateProjectWithVersion(opts.FromVersion)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\t\"failed to run alpha generate on ancestor branch\",\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"PrepareAncestorBranch\", func() {\n\t\tIt(\"succeeds\", func() {\n\t\t\terr = opts.prepareAncestorBranch()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tverifyLogs(opts.AncestorBranch, opts.FromBranch, opts.FromVersion)\n\t\t})\n\n\t\tIt(\"fails creating branch\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockGit)).To(Succeed())\n\n\t\t\terr = opts.prepareAncestorBranch()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to create %s from %s\",\n\t\t\t\t\topts.AncestorBranch, opts.FromBranch),\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"PrepareUpgradeBranch\", func() {\n\t\tIt(\"succeeds\", func() {\n\t\t\terr = opts.prepareUpgradeBranch()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tverifyLogs(opts.UpgradeBranch, opts.AncestorBranch, opts.ToVersion)\n\t\t})\n\n\t\tIt(\"fails creating branch\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockGit)).To(Succeed())\n\n\t\t\terr = opts.prepareUpgradeBranch()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to checkout %s branch off %s\",\n\t\t\t\t\topts.UpgradeBranch, opts.AncestorBranch),\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"BinaryWithVersion\", func() {\n\t\tIt(\"succeeds to download the specified released version\", func() {\n\t\t\t_, err = helpers.DownloadReleaseVersionWith(opts.FromVersion)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"fails to download the specified released version\", func() {\n\t\t\tgock.Off()\n\t\t\tgock.New(\"https://github.com\").\n\t\t\t\tGet(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\t\tTimes(2).Reply(401).Body(strings.NewReader(\"\"))\n\n\t\t\t_, err = helpers.DownloadReleaseVersionWith(opts.FromVersion)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(Equal(\"failed to download the binary: HTTP 401\"))\n\t\t})\n\t})\n\n\tContext(\"CleanupBranch\", func() {\n\t\tIt(\"succeeds executing cleanup command\", func() {\n\t\t\terr = cleanupBranch()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"fails executing cleanup command\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mocksh)).To(Succeed())\n\n\t\t\terr = cleanupBranch()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to clean up files\"))\n\t\t})\n\t})\n\n\tContext(\"RunMakeTargets\", func() {\n\t\tIt(\"logs warning when make fails\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockMake)).To(Succeed())\n\n\t\t\t// Should not panic even if make fails; just logs a warning.\n\t\t\trunMakeTargets(false)\n\t\t})\n\t})\n\n\tContext(\"RunAlphaGenerate\", func() {\n\t\tIt(\"succeeds\", func() {\n\t\t\tmockKB := filepath.Join(tmpDir, \"kubebuilder\")\n\t\t\tscript := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 0`\n\t\t\tExpect(mockBinResponse(script, mockKB)).To(Succeed())\n\n\t\t\terr = runAlphaGenerate(tmpDir, opts.FromVersion)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).NotTo(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\"alpha generate\"))\n\t\t})\n\n\t\tIt(\"fails\", func() {\n\t\t\tmockKB := filepath.Join(tmpDir, \"kubebuilder\")\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockKB)).To(Succeed())\n\n\t\t\terr = runAlphaGenerate(tmpDir, opts.FromVersion)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to run alpha generate\"))\n\t\t})\n\t})\n\n\tContext(\"PrepareOriginalBranch\", func() {\n\t\tIt(\"succeeds\", func() {\n\t\t\terr = opts.prepareOriginalBranch()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\ts := string(logs)\n\n\t\t\tExpect(s).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout -b %s\", opts.OriginalBranch),\n\t\t\t))\n\t\t\tExpect(s).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout %s -- .\", opts.FromBranch),\n\t\t\t))\n\t\t\tExpect(s).To(ContainSubstring(\"add --all\"))\n\t\t\tExpect(s).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"original code from %s to keep changes\", opts.FromBranch),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"fails\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockGit)).To(Succeed())\n\n\t\t\terr = opts.prepareOriginalBranch()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to checkout branch %s\", opts.OriginalBranch),\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"MergeOriginalToUpgrade\", func() {\n\t\tBeforeEach(func() {\n\t\t\t// deterministic names for merge test\n\t\t\topts.UpgradeBranch = \"tmp-upgrade-X\"\n\t\t\topts.MergeBranch = \"tmp-merge-X\"\n\t\t\topts.OriginalBranch = \"tmp-original-X\"\n\t\t})\n\n\t\tIt(\"succeeds and commits with normal message\", func() {\n\t\t\t_, err = opts.mergeOriginalToUpgrade()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\ts := string(logs)\n\n\t\t\tExpect(s).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout -b %s %s\", opts.MergeBranch, opts.UpgradeBranch),\n\t\t\t))\n\t\t\tExpect(s).To(ContainSubstring(fmt.Sprintf(\"checkout %s\", opts.MergeBranch)))\n\t\t\tExpect(s).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"merge --no-edit --no-commit %s\", opts.OriginalBranch),\n\t\t\t))\n\t\t\tExpect(s).To(ContainSubstring(\"add --all\"))\n\t\t\tExpect(s).To(ContainSubstring(helpers.MergeCommitMessage(opts.FromVersion, opts.ToVersion)))\n\t\t})\n\n\t\tIt(\"fails when branch creation fails\", func() {\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, mockGit)).To(Succeed())\n\n\t\t\t_, err = opts.mergeOriginalToUpgrade()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"failed to create merge branch %s from %s\",\n\t\t\t\t\topts.MergeBranch, opts.UpgradeBranch),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"stops on conflicts when Force=false\", func() {\n\t\t\tfailOnMerge := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"merge\" ]]; then exit 1; fi\nexit 0`\n\t\t\tExpect(mockBinResponse(failOnMerge, mockGit)).To(Succeed())\n\n\t\t\topts.Force = false\n\t\t\t_, err = opts.mergeOriginalToUpgrade()\n\t\t\tExpect(err).To(HaveOccurred())\n\n\t\t\ts, _ := os.ReadFile(logFile)\n\t\t\tExpect(string(s)).NotTo(ContainSubstring(\"commit --no-verify -m\"))\n\t\t})\n\n\t\tIt(\"commits with conflict message when Force=true\", func() {\n\t\t\tfailOnMerge := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"merge\" ]]; then exit 1; fi\nexit 0`\n\t\t\tExpect(mockBinResponse(failOnMerge, mockGit)).To(Succeed())\n\n\t\t\topts.Force = true\n\t\t\t_, err = opts.mergeOriginalToUpgrade()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\ts, _ := os.ReadFile(logFile)\n\t\t\tExpect(string(s)).To(ContainSubstring(\n\t\t\t\thelpers.ConflictCommitMessage(opts.FromVersion, opts.ToVersion),\n\t\t\t))\n\t\t})\n\t})\n\n\tContext(\"SquashToOutputBranch\", func() {\n\t\tBeforeEach(func() {\n\t\t\topts.FromBranch = defaultBranch\n\t\t\topts.FromVersion = \"v4.5.0\"\n\t\t\topts.ToVersion = \"v4.6.0\"\n\t\t\tif opts.MergeBranch == \"\" {\n\t\t\t\topts.MergeBranch = \"tmp-merge-test\"\n\t\t\t}\n\t\t})\n\n\t\tIt(\"creates/resets output branch and commits one squashed snapshot\", func() {\n\t\t\topts.OutputBranch = \"\" // default naming\n\t\t\topts.RestorePath = []string{\".github/workflows\"}\n\t\t\topts.ShowCommits = false\n\n\t\t\terr = opts.squashToOutputBranch(false) // no conflicts\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\ts := string(logs)\n\n\t\t\tExpect(s).To(ContainSubstring(fmt.Sprintf(\"checkout %s\", opts.FromBranch)))\n\n\t\t\texpOut := fmt.Sprintf(\n\t\t\t\t\"checkout -B %s %s\",\n\t\t\t\tfmt.Sprintf(\"kubebuilder-update-from-%s-to-%s\",\n\t\t\t\t\topts.FromVersion, opts.ToVersion),\n\t\t\t\topts.FromBranch,\n\t\t\t)\n\t\t\tExpect(s).To(ContainSubstring(expOut))\n\n\t\t\tExpect(s).To(ContainSubstring(fmt.Sprintf(\"checkout %s -- .\", opts.MergeBranch)))\n\t\t\tExpect(s).To(ContainSubstring(\"add --all\"))\n\t\t\tExpect(s).To(ContainSubstring(helpers.MergeCommitMessage(opts.FromVersion, opts.ToVersion)))\n\t\t\tExpect(s).To(ContainSubstring(\"commit --no-verify -m\"))\n\t\t})\n\n\t\tIt(\"respects a custom output branch name\", func() {\n\t\t\topts.OutputBranch = \"my-custom-branch\"\n\n\t\t\terr = opts.squashToOutputBranch(false)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tlogs, _ := os.ReadFile(logFile)\n\t\t\tExpect(string(logs)).To(ContainSubstring(\n\t\t\t\tfmt.Sprintf(\"checkout -B %s %s\", \"my-custom-branch\", opts.FromBranch),\n\t\t\t))\n\t\t})\n\n\t\tIt(\"no changes -> commit exits 1 but helper returns nil\", func() {\n\t\t\tfake := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nif [[ \"$1\" == \"commit\" ]]; then exit 1; fi\nexit 0`\n\t\t\tExpect(mockBinResponse(fake, mockGit)).To(Succeed())\n\n\t\t\topts.RestorePath = nil\n\t\t\tExpect(opts.squashToOutputBranch(false)).To(Succeed())\n\n\t\t\ts, _ := os.ReadFile(logFile)\n\t\t\tExpect(string(s)).To(ContainSubstring(\"commit --no-verify -m\"))\n\t\t})\n\n\t\tIt(\"trims restore-path and skips blanks\", func() {\n\t\t\topts.RestorePath = []string{\" .github/workflows \", \"\", \"docs\"}\n\t\t\tExpect(opts.squashToOutputBranch(false)).To(Succeed())\n\n\t\t\ts, _ := os.ReadFile(logFile)\n\t\t\tExpect(string(s)).To(ContainSubstring(\"checkout main -- docs\"))\n\t\t\tExpect(string(s)).To(ContainSubstring(\"checkout main -- .github/workflows\"))\n\t\t})\n\t})\n\n\tContext(\"getOutputBranchName\", func() {\n\t\tIt(\"returns default name when OutputBranch is empty\", func() {\n\t\t\tconst fromVersion = \"v4.5.0\"\n\t\t\tconst toVersion = \"v4.6.0\"\n\t\t\topts.FromVersion = fromVersion\n\t\t\topts.ToVersion = toVersion\n\t\t\topts.OutputBranch = \"\"\n\n\t\t\twant := fmt.Sprintf(\"kubebuilder-update-from-%s-to-%s\", fromVersion, toVersion)\n\t\t\tExpect(opts.getOutputBranchName()).To(Equal(want))\n\t\t})\n\n\t\tIt(\"returns custom name when OutputBranch is set\", func() {\n\t\t\topts.OutputBranch = \"my-custom\"\n\t\t\tExpect(opts.getOutputBranchName()).To(Equal(\"my-custom\"))\n\t\t})\n\t})\n\n\tContext(\"runAlphaGenerate PATH restoration\", func() {\n\t\tIt(\"does not mutate process PATH (same even on failure)\", func() {\n\t\t\ttmp := filepath.Join(tmpDir, \"kubebuilder\")\n\t\t\tfail := `#!/bin/bash\necho \"$@\" >> \"` + logFile + `\"\nexit 1`\n\t\t\tExpect(mockBinResponse(fail, tmp)).To(Succeed())\n\n\t\t\torig := os.Getenv(\"PATH\")\n\t\t\terr := runAlphaGenerate(tmpDir, \"v4.5.0\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(os.Getenv(\"PATH\")).To(Equal(orig))\n\t\t})\n\t})\n\n\tContext(\"getMergeMessage\", func() {\n\t\tBeforeEach(func() {\n\t\t\topts.FromVersion = \"v4.5.0\"\n\t\t\topts.ToVersion = \"v4.6.0\"\n\t\t\topts.CommitMessage = \"\"\n\t\t\topts.CommitMessageConflict = \"\"\n\t\t})\n\n\t\tIt(\"uses custom commit message when provided (no conflicts)\", func() {\n\t\t\topts.CommitMessage = \"chore: custom update message\"\n\t\t\tmsg := opts.getMergeMessage(false)\n\t\t\tExpect(msg).To(Equal(\"chore: custom update message\"))\n\t\t})\n\n\t\tIt(\"uses custom conflict message when provided (with conflicts)\", func() {\n\t\t\topts.CommitMessageConflict = \"chore: custom conflict message\"\n\t\t\tmsg := opts.getMergeMessage(true)\n\t\t\tExpect(msg).To(Equal(\"chore: custom conflict message\"))\n\t\t})\n\n\t\tIt(\"uses default message when no custom message (no conflicts)\", func() {\n\t\t\tmsg := opts.getMergeMessage(false)\n\t\t\tExpect(msg).To(Equal(helpers.MergeCommitMessage(opts.FromVersion, opts.ToVersion)))\n\t\t})\n\n\t\tIt(\"uses default conflict message when no custom message (with conflicts)\", func() {\n\t\t\tmsg := opts.getMergeMessage(true)\n\t\t\tExpect(msg).To(Equal(helpers.ConflictCommitMessage(opts.FromVersion, opts.ToVersion)))\n\t\t})\n\n\t\tIt(\"prefers conflict message over regular message when conflicts occur\", func() {\n\t\t\topts.CommitMessage = \"chore: regular message\"\n\t\t\topts.CommitMessageConflict = \"chore: conflict message\"\n\t\t\tmsg := opts.getMergeMessage(true)\n\t\t\tExpect(msg).To(Equal(\"chore: conflict message\"))\n\t\t})\n\n\t\tIt(\"falls back to default conflict message when only regular message is set\", func() {\n\t\t\topts.CommitMessage = \"chore: regular message\"\n\t\t\tmsg := opts.getMergeMessage(true)\n\t\t\tExpect(msg).To(Equal(helpers.ConflictCommitMessage(opts.FromVersion, opts.ToVersion)))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/validate.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"golang.org/x/mod/semver\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/update/helpers\"\n)\n\n// Validate checks the input info provided for the update and populates the cliVersion\nfunc (opts *Update) Validate() error {\n\tif err := opts.validateEqualVersions(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate equal versions: %w\", err)\n\t}\n\tif err := opts.validateGitRepo(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate git repository: %w\", err)\n\t}\n\tif err := opts.validateFromBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate --from-branch: %w\", err)\n\t}\n\tif err := opts.validateSemanticVersions(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate the versions: %w\", err)\n\t}\n\tif err := validateReleaseAvailability(opts.FromVersion); err != nil {\n\t\treturn fmt.Errorf(\"unable to find release %s: %w\", opts.FromVersion, err)\n\t}\n\tif err := validateReleaseAvailability(opts.ToVersion); err != nil {\n\t\treturn fmt.Errorf(\"unable to find release %s: %w\", opts.ToVersion, err)\n\t}\n\n\tif opts.OpenGhIssue {\n\t\tif err := exec.Command(\"gh\", \"--version\").Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"`gh` CLI not found or not authenticated. \"+\n\t\t\t\t\"You must have gh instaled to use the --open-gh-issue option: %s\", err)\n\t\t}\n\t}\n\n\tif opts.UseGhModels && !isGhModelsExtensionInstalled() {\n\t\treturn fmt.Errorf(\"gh-models extension is not installed. To install the extension, run: \" +\n\t\t\t\"gh extension install https://github.com/github/gh-models\")\n\t}\n\n\treturn nil\n}\n\n// isGhModelsExtensionInstalled checks if the gh-models extension is installed\nfunc isGhModelsExtensionInstalled() bool {\n\tcmd := exec.Command(\"gh\", \"extension\", \"list\")\n\tif _, err := cmd.Output(); err != nil {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// validateGitRepo verifies if the current directory is a valid Git repository and checks for uncommitted changes.\nfunc (opts *Update) validateGitRepo() error {\n\tlog.Info(\"Checking if is a git repository\")\n\tgitCmd := exec.Command(\"git\", \"rev-parse\", \"--git-dir\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"not in a git repository\")\n\t}\n\n\tlog.Info(\"Checking if branch has uncommitted changes\")\n\tgitCmd = exec.Command(\"git\", \"status\", \"--porcelain\")\n\toutput, err := gitCmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check branch status: %w\", err)\n\t}\n\tif len(strings.TrimSpace(string(output))) > 0 {\n\t\treturn fmt.Errorf(\"working directory has uncommitted changes. \" +\n\t\t\t\"Please commit or stash them before updating\")\n\t}\n\treturn nil\n}\n\n// validateFromBranch the branch passed to the --from-branch flag\nfunc (opts *Update) validateFromBranch() error {\n\t// Check if the branch exists\n\tgitCmd := exec.Command(\"git\", \"rev-parse\", \"--verify\", opts.FromBranch)\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"%s branch does not exist locally. \"+\n\t\t\t\"Run 'git branch -a' to see all available branches\",\n\t\t\topts.FromBranch)\n\t}\n\treturn nil\n}\n\n// validateSemanticVersions the version informed by the user via --from-version flag\nfunc (opts *Update) validateSemanticVersions() error {\n\tif !semver.IsValid(opts.FromVersion) {\n\t\treturn fmt.Errorf(\" version informed (%s) has invalid semantic version. \"+\n\t\t\t\"Expect: vX.Y.Z (Ex: v4.5.0)\", opts.FromVersion)\n\t}\n\tif !semver.IsValid(opts.ToVersion) {\n\t\treturn fmt.Errorf(\" version informed (%s) has invalid semantic version. \"+\n\t\t\t\"Expect: vX.Y.Z (Ex: v4.5.0)\", opts.ToVersion)\n\t}\n\treturn nil\n}\n\n// validateReleaseAvailability will verify if the binary to scaffold from-version flag is available\nfunc validateReleaseAvailability(version string) error {\n\turl := helpers.BuildReleaseURL(version)\n\tresp, err := http.Head(url)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check binary availability: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err = resp.Body.Close(); err != nil {\n\t\t\tlog.Error(\"failed to close connection\", \"error\", err)\n\t\t}\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tlog.Info(\"Binary version available\", \"version\", version)\n\t\treturn nil\n\tcase http.StatusNotFound:\n\t\treturn fmt.Errorf(\"binary version %s not found. Check versions available in releases\",\n\t\t\tversion)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected response %d when checking binary availability for version %s\",\n\t\t\tresp.StatusCode, version)\n\t}\n}\n\n// validateEqualVersions checks if from-version and to-version are the same.\n// If they are equal, logs an appropriate message and exits successfully.\nfunc (opts *Update) validateEqualVersions() error {\n\tif opts.FromVersion == opts.ToVersion {\n\t\t// Check if this is the latest version to provide appropriate message\n\t\tlatestVersion, err := fetchLatestRelease()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to fetch latest release for messaging: %w\", err)\n\t\t}\n\n\t\tif opts.ToVersion == latestVersion {\n\t\t\tlog.Info(\"Your project already uses the latest version. No action taken.\", \"version\", opts.FromVersion)\n\t\t} else {\n\t\t\tlog.Info(\"Your project already uses the specified version. No action taken.\", \"version\", opts.FromVersion)\n\t\t}\n\t\tos.Exit(0)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/internal/update/validate_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage update\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/h2non/gock\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Prepare for internal update\", func() {\n\tvar (\n\t\ttmpDir  string\n\t\tmockGit string\n\t\tlogFile string\n\t\toldPath string\n\t\terr     error\n\t\topts    *Update\n\t)\n\n\tBeforeEach(func() {\n\t\topts = &Update{\n\t\t\tFromVersion:    \"v4.5.0\",\n\t\t\tToVersion:      \"v4.6.0\",\n\t\t\tFromBranch:     defaultBranch,\n\t\t\tOriginalBranch: \"v4.6.0\",\n\t\t}\n\n\t\t// Create temporary directory to house fake bin executables\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"temp-bin\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Create a common file to log the command runs from the fake bin\n\t\tlogFile = filepath.Join(tmpDir, \"bin.log\")\n\n\t\t// Create fake bin executables\n\t\tmockGit = filepath.Join(tmpDir, \"git\")\n\t\tscript := `#!/bin/bash\n            echo \"$@\" >> \"` + logFile + `\"\n           exit 0`\n\t\terr = mockBinResponse(script, mockGit)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Prepend temp bin directory to PATH env\n\t\toldPath = os.Getenv(\"PATH\")\n\t\terr = os.Setenv(\"PATH\", tmpDir+\":\"+oldPath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tgock.New(\"https://github.com\").\n\t\t\tHead(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\tTimes(2).\n\t\t\tReply(200).\n\t\t\tBody(strings.NewReader(\"body\"))\n\t})\n\n\tAfterEach(func() {\n\t\t_ = os.RemoveAll(tmpDir)\n\t\t_ = os.Setenv(\"PATH\", oldPath)\n\t\tdefer gock.Off()\n\t})\n\n\tContext(\"Validate\", func() {\n\t\tIt(\"Should scucceed\", func() {\n\t\t\terr = opts.Validate()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\t\tIt(\"Should fail\", func() {\n\t\t\tfakeBinScript := `#!/bin/bash\n\t\t\t    \techo \"$@\" >> \"` + logFile + `\"\n\t\t\t\t\texit 1`\n\t\t\terr = mockBinResponse(fakeBinScript, mockGit)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\terr = opts.Validate()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"failed to validate git repository\"))\n\t\t})\n\t})\n\n\tContext(\"ValidateGitRepo\", func() {\n\t\tIt(\"Should scucceed\", func() {\n\t\t\terr = opts.validateGitRepo()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\"rev-parse --git-dir\"))\n\t\t\tExpect(string(logs)).To(ContainSubstring(\"status --porcelain\"))\n\t\t})\n\t\tIt(\"Should fail\", func() {\n\t\t\tfakeBinScript := `#!/bin/bash\n\t\t\t    \techo \"$@\" >> \"` + logFile + `\"\n\t\t\t\t\texit 1`\n\t\t\terr = mockBinResponse(fakeBinScript, mockGit)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\terr = opts.validateGitRepo()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"not in a git repository\"))\n\t\t})\n\t})\n\n\tContext(\"ValidateFromBranch\", func() {\n\t\tIt(\"Should scucceed\", func() {\n\t\t\terr = opts.validateFromBranch()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tlogs, readErr := os.ReadFile(logFile)\n\t\t\tExpect(readErr).ToNot(HaveOccurred())\n\t\t\tExpect(string(logs)).To(ContainSubstring(\"rev-parse --verify %s\", opts.FromBranch))\n\t\t})\n\t\tIt(\"Should fail\", func() {\n\t\t\tfakeBinScript := `#!/bin/bash\n\t\t\t       echo \"$@\" >> \"` + logFile + `\"\n\t\t\t       exit 1`\n\t\t\terr = mockBinResponse(fakeBinScript, mockGit)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\terr := opts.validateFromBranch()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"branch does not exist locally\"))\n\t\t})\n\t})\n\n\tContext(\"ValidateSemanticVersions\", func() {\n\t\tIt(\"Should scucceed\", func() {\n\t\t\terr := opts.validateSemanticVersions()\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\t\tIt(\"Should fail\", func() {\n\t\t\topts.FromVersion = \"6\"\n\t\t\terr := opts.validateSemanticVersions()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"has invalid semantic version. Expect: vX.Y.Z\"))\n\t\t})\n\t})\n\n\tContext(\"ValidateReleaseAvailability\", func() {\n\t\tIt(\"Should scucceed\", func() {\n\t\t\terr := validateReleaseAvailability(opts.ToVersion)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\t\tIt(\"Should fail\", func() {\n\t\t\tgock.Off()\n\t\t\tgock.New(\"https://github.com\").\n\t\t\t\tHead(\"/kubernetes-sigs/kubebuilder/releases/download\").\n\t\t\t\tReply(401).\n\t\t\t\tBody(strings.NewReader(\"body\"))\n\t\t\terr := validateReleaseAvailability(opts.FromVersion)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"unexpected response\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/alpha/internal/update.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage internal\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tlog \"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\t\"golang.org/x/mod/semver\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n)\n\n// Update contains configuration for the update operation\ntype Update struct {\n\t// FromVersion specifies which version of Kubebuilder to use for the update.\n\t// If empty, the version from the PROJECT file will be used.\n\tFromVersion string\n\t// FromBranch specifies which branch to use as current when updating\n\tFromBranch string\n\t// CliVersion holds the version to be used during the upgrade process\n\tCliVersion string\n\t// BinaryURL holds the URL for downloading the specified binary from\n\t// the releases on GitHub\n\tBinaryURL string\n}\n\n// Update performs a complete project update by creating a three-way merge to help users\n// upgrade their Kubebuilder projects. The process creates multiple Git branches:\n// - ancestor: Clean state with old Kubebuilder version scaffolding\n// - current: User's current project state\n// - upgrade: New Kubebuilder version scaffolding\n// - merge: Attempts to merge upgrade changes into current state\nfunc (opts *Update) Update() error {\n\t// Download the specific Kubebuilder binary version for generating clean scaffolding\n\ttempDir, err := opts.downloadKubebuilderBinary()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to download Kubebuilder %s binary: %w\", opts.CliVersion, err)\n\t}\n\tlog.Info(\"Downloaded binary kept for debugging purposes\", \"directory\", tempDir)\n\n\t// Create ancestor branch with clean state for three-way merge\n\tif err := opts.checkoutAncestorBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout the ancestor branch: %w\", err)\n\t}\n\n\t// Remove all existing files to create a clean slate for re-scaffolding\n\tif err := opts.cleanUpAncestorBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to clean up the ancestor branch: %w\", err)\n\t}\n\n\t// Generate clean scaffolding using the old Kubebuilder version\n\tif err := opts.runAlphaGenerate(tempDir, opts.CliVersion); err != nil {\n\t\treturn fmt.Errorf(\"failed to run alpha generate on ancestor branch: %w\", err)\n\t}\n\n\t// Create current branch representing user's existing project state\n\tif err := opts.checkoutCurrentOffAncestor(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout current off ancestor: %w\", err)\n\t}\n\n\t// Create upgrade branch with new Kubebuilder version scaffolding\n\tif err := opts.checkoutUpgradeOffAncestor(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout upgrade off ancestor: %w\", err)\n\t}\n\n\t// Create merge branch to attempt automatic merging of changes\n\tif err := opts.checkoutMergeOffCurrent(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout merge branch off current: %w\", err)\n\t}\n\n\t// Attempt to merge upgrade changes into the user's current state\n\tif err := opts.mergeUpgradeIntoMerge(); err != nil {\n\t\treturn fmt.Errorf(\"failed to merge upgrade into merge branch: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// downloadKubebuilderBinary downloads the specified version of Kubebuilder binary\n// from GitHub releases and saves it to a temporary directory with executable permissions.\n// Returns the temporary directory path containing the binary.\nfunc (opts *Update) downloadKubebuilderBinary() (string, error) {\n\t// Construct GitHub release URL based on current OS and architecture\n\turl := opts.BinaryURL\n\n\tlog.Info(\"Downloading the Kubebuilder binary\", \"version\", opts.CliVersion, \"download_url\", url)\n\n\t// Create temporary directory for storing the downloaded binary\n\tfs := afero.NewOsFs()\n\ttempDir, err := afero.TempDir(fs, \"\", \"kubebuilder\"+opts.CliVersion+\"-\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temporary directory: %w\", err)\n\t}\n\n\t// Create the binary file in the temporary directory\n\tbinaryPath := tempDir + \"/kubebuilder\"\n\tfile, err := os.Create(binaryPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create the binary file: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err = file.Close(); err != nil {\n\t\t\tlog.Error(\"failed to close the file\", \"error\", err)\n\t\t}\n\t}()\n\n\t// Download the binary from GitHub releases\n\tresponse, err := http.Get(url)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to download the binary: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err = response.Body.Close(); err != nil {\n\t\t\tlog.Error(\"failed to close the connection\", \"error\", err)\n\t\t}\n\t}()\n\n\t// Check if download was successful\n\tif response.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to download the binary: HTTP %d\", response.StatusCode)\n\t}\n\n\t// Copy the downloaded content to the local file\n\t_, err = io.Copy(file, response.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to write the binary content to file: %w\", err)\n\t}\n\n\t// Make the binary executable\n\tif err := os.Chmod(binaryPath, 0o755); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to make binary executable: %w\", err)\n\t}\n\n\tlog.Info(\"Kubebuilder successfully downloaded\", \"kubebuilder_version\", opts.CliVersion, \"binary_path\", binaryPath)\n\n\treturn tempDir, nil\n}\n\n// checkoutAncestorBranch creates and switches to the 'ancestor' branch.\n// This branch will serve as the common ancestor for the three-way merge,\n// containing clean scaffolding from the old Kubebuilder version.\nfunc (opts *Update) checkoutAncestorBranch() error {\n\tgitCmd := exec.Command(\"git\", \"checkout\", \"-b\", \"tmp-kb-update-ancestor\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to create and checkout ancestor branch: %w\", err)\n\t}\n\tlog.Info(\"Created and checked out ancestor branch\")\n\n\treturn nil\n}\n\n// cleanUpAncestorBranch removes all files from the ancestor branch to create\n// a clean state for re-scaffolding. This ensures the ancestor branch only\n// contains pure scaffolding without any user modifications.\nfunc (opts *Update) cleanUpAncestorBranch() error {\n\tlog.Info(\"Cleaning all files and folders except .git and PROJECT\")\n\t// Remove all tracked files from the Git repository\n\tcmd := exec.Command(\"find\", \".\", \"-mindepth\", \"1\", \"-maxdepth\", \"1\",\n\t\t\"!\", \"-name\", \".git\",\n\t\t\"!\", \"-name\", \"PROJECT\",\n\t\t\"-exec\", \"rm\", \"-rf\", \"{}\", \"+\")\n\tlog.Info(\"Running cleanup command\", \"command\", cmd.Args)\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to clean up files in ancestor branch: %w\", err)\n\t}\n\tlog.Info(\"Successfully cleanup files in ancestor branch\")\n\n\t// Remove all untracked files and directories\n\tgitCmd := exec.Command(\"git\", \"add\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage changes in ancestor: %w\", err)\n\t}\n\tlog.Info(\"Successfully staged changes in ancestor\")\n\n\t// Commit the cleanup to establish the clean state\n\tgitCmd = exec.Command(\"git\", \"commit\", \"-m\", \"Clean up the ancestor branch\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit the cleanup in ancestor branch: %w\", err)\n\t}\n\tlog.Info(\"Successfully committed cleanup on ancestor\")\n\n\treturn nil\n}\n\n// runMakeTargets is a helper function to run make with the targets necessary\n// to ensure all the necessary components are generated, formatted and linted.\nfunc runMakeTargets() error {\n\ttargets := []string{\"manifests\", \"generate\", \"fmt\", \"vet\", \"lint-fix\"}\n\tfor _, target := range targets {\n\t\terr := util.RunCmd(fmt.Sprintf(\"Running make %s\", target), \"make\", target)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"make %s failed: %v\", target, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// runAlphaGenerate executes the old Kubebuilder version's 'alpha generate' command\n// to create clean scaffolding in the ancestor branch. This uses the downloaded\n// binary with the original PROJECT file to recreate the project's initial state.\nfunc (opts *Update) runAlphaGenerate(tempDir, version string) error {\n\t// Temporarily modify PATH to use the downloaded Kubebuilder binary\n\ttempBinaryPath := tempDir + \"/kubebuilder\"\n\toriginalPath := os.Getenv(\"PATH\")\n\ttempEnvPath := tempDir + \":\" + originalPath\n\n\tif err := os.Setenv(\"PATH\", tempEnvPath); err != nil {\n\t\treturn fmt.Errorf(\"failed to set temporary PATH: %w\", err)\n\t}\n\n\t// Restore original PATH when function completes\n\tdefer func() {\n\t\tif err := os.Setenv(\"PATH\", originalPath); err != nil {\n\t\t\tlog.Error(\"failed to restore original PATH\", \"error\", err)\n\t\t}\n\t}()\n\n\t// Prepare the alpha generate command with proper I/O redirection\n\tcmd := exec.Command(tempBinaryPath, \"alpha\", \"generate\")\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tcmd.Env = os.Environ()\n\n\t// Execute the alpha generate command to create clean scaffolding\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run alpha generate: %w\", err)\n\t}\n\tlog.Info(\"Successfully ran alpha generate using Kubebuilder\", \"version\", version)\n\n\t// Run make targets to ensure all the necessary components are generated,\n\t// formatted and linted.\n\tlog.Info(\"Running 'make manifests generate fmt vet lint-fix'\")\n\tif err := runMakeTargets(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run make: %w\", err)\n\t}\n\tlog.Info(\"Successfully ran make targets in ancestor\")\n\n\t// Stage all generated files\n\tgitCmd := exec.Command(\"git\", \"add\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage changes in ancestor: %w\", err)\n\t}\n\tlog.Info(\"Successfully staged all changes in ancestor\")\n\n\t// Commit the re-scaffolded project to the ancestor branch\n\tgitCmd = exec.Command(\"git\", \"commit\", \"-m\", \"Re-scaffold in ancestor\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit changes in ancestor: %w\", err)\n\t}\n\tlog.Info(\"Successfully committed changes in ancestor\")\n\n\treturn nil\n}\n\n// checkoutCurrentOffAncestor creates the 'current' branch from ancestor and\n// populates it with the user's actual project content from the default branch.\n// This represents the current state of the user's project.\nfunc (opts *Update) checkoutCurrentOffAncestor() error {\n\t// Create current branch starting from the clean ancestor state\n\tgitCmd := exec.Command(\"git\", \"checkout\", \"-b\", \"tmp-kb-update-current\", \"tmp-kb-update-ancestor\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout current branch off ancestor: %w\", err)\n\t}\n\tlog.Info(\"Successfully checked out current branch off ancestor\")\n\n\t// Overlay the user's actual project content from default branch\n\tgitCmd = exec.Command(\"git\", \"checkout\", opts.FromBranch, \"--\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout content from default branch onto current: %w\", err)\n\t}\n\tlog.Info(\"Successfully checked out content from main onto current branch\")\n\n\t// Stage all the user's current project content\n\tgitCmd = exec.Command(\"git\", \"add\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage all changes in current: %w\", err)\n\t}\n\tlog.Info(\"Successfully staged all changes in current\")\n\n\t// Commit the user's current state to the current branch\n\tgitCmd = exec.Command(\"git\", \"commit\", \"-m\", \"Add content from main onto current branch\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit changes: %w\", err)\n\t}\n\tlog.Info(\"Successfully committed changes in current\")\n\n\treturn nil\n}\n\n// checkoutUpgradeOffAncestor creates the 'upgrade' branch from ancestor and\n// generates fresh scaffolding using the current (latest) Kubebuilder version.\n// This represents what the project should look like with the new version.\nfunc (opts *Update) checkoutUpgradeOffAncestor() error {\n\t// Create upgrade branch starting from the clean ancestor state\n\tgitCmd := exec.Command(\"git\", \"checkout\", \"-b\", \"tmp-kb-update-upgrade\", \"tmp-kb-update-ancestor\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout upgrade branch off ancestor: %w\", err)\n\t}\n\tlog.Info(\"Successfully checked out upgrade branch off ancestor\")\n\n\t// Run alpha generate with the current (new) Kubebuilder version\n\t// This uses the system's installed kubebuilder binary\n\tcmd := exec.Command(\"kubebuilder\", \"alpha\", \"generate\")\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run alpha generate on upgrade branch: %w\", err)\n\t}\n\tlog.Info(\"Successfully ran alpha generate on upgrade branch\")\n\n\t// Run make targets to ensure all the necessary components are generated,\n\t// formatted and linted.\n\tlog.Info(\"Running 'make manifests generate fmt vet lint-fix'\")\n\tif err := runMakeTargets(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run make: %w\", err)\n\t}\n\tlog.Info(\"Successfully ran make targets in upgrade\")\n\n\t// Stage all the newly generated files\n\tgitCmd = exec.Command(\"git\", \"add\", \".\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stage changes on upgrade: %w\", err)\n\t}\n\tlog.Info(\"Successfully staged all changes in upgrade branch\")\n\n\t// Commit the new version's scaffolding to the upgrade branch\n\tgitCmd = exec.Command(\"git\", \"commit\", \"-m\", \"alpha generate in upgrade branch\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to commit changes in upgrade branch: %w\", err)\n\t}\n\tlog.Info(\"Successfully committed changes in upgrade branch\")\n\n\treturn nil\n}\n\n// checkoutMergeOffCurrent creates the 'merge' branch from the current branch.\n// This branch will be used to attempt automatic merging of upgrade changes\n// with the user's current project state.\nfunc (opts *Update) checkoutMergeOffCurrent() error {\n\tgitCmd := exec.Command(\"git\", \"checkout\", \"-b\", \"tmp-kb-update-merge\", \"tmp-kb-update-current\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to checkout merge branch off current: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// mergeUpgradeIntoMerge attempts to merge the upgrade branch (containing new\n// Kubebuilder scaffolding) into the merge branch (containing user's current state).\n// If conflicts occur, it warns the user to resolve them manually rather than failing.\nfunc (opts *Update) mergeUpgradeIntoMerge() error {\n\tgitCmd := exec.Command(\"git\", \"merge\", \"upgrade\")\n\terr := gitCmd.Run()\n\tif err != nil {\n\t\t// Check if the error is due to merge conflicts (exit code 1)\n\t\tif exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {\n\t\t\tlog.Warn(\"Merge with conflicts. Please resolve them manually\")\n\t\t\treturn nil // Don't treat conflicts as fatal errors\n\t\t}\n\t\treturn fmt.Errorf(\"failed to merge the upgrade branch into the merge branch: %w\", err)\n\t}\n\n\t// Run make targets to ensure all the necessary components are generated,\n\t// formatted and linted.\n\tlog.Info(\"Running 'make manifests generate fmt vet lint-fix'\")\n\tif err := runMakeTargets(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run make: %w\", err)\n\t}\n\tlog.Info(\"Successfully ran make targets in merge\")\n\n\treturn nil\n}\n\n// Validate checks if the user is in a git repository and if the repository is in a clean state.\n// It also validates if the version specified by the user is in a valid format and available for\n// download as a binary.\nfunc (opts *Update) Validate() error {\n\t// Validate git repository\n\tif err := opts.validateGitRepo(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate git repository: %w\", err)\n\t}\n\n\t// Validate --from-branch\n\tif err := opts.validateFromBranch(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate --from-branch: %w\", err)\n\t}\n\n\t// Load the PROJECT configuration file\n\tprojectConfigFile, err := opts.loadConfigFile()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load the PROJECT file: %w\", err)\n\t}\n\n\t// Extract the cliVersion field from the PROJECT file\n\topts.CliVersion = projectConfigFile.Config().GetCliVersion()\n\n\t// Determine which Kubebuilder version to use for the update\n\tif err := opts.defineFromVersion(); err != nil {\n\t\treturn fmt.Errorf(\"failed to define version: %w\", err)\n\t}\n\n\t// Validate if the specified version is available as a binary in the releases\n\tif err := opts.validateBinaryAvailability(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate binary availability: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Load the PROJECT configuration file to get the current CLI version\nfunc (opts *Update) loadConfigFile() (store.Store, error) {\n\tprojectConfigFile := yaml.New(machinery.Filesystem{FS: afero.NewOsFs()})\n\t// TODO: assess if DefaultPath could be renamed to a more self-descriptive name\n\tif err := projectConfigFile.LoadFrom(yaml.DefaultPath); err != nil {\n\t\tif _, statErr := os.Stat(yaml.DefaultPath); os.IsNotExist(statErr) {\n\t\t\treturn projectConfigFile, fmt.Errorf(\"no PROJECT file found. Make sure you're in the project root directory\")\n\t\t}\n\t\treturn projectConfigFile, fmt.Errorf(\"fail to load the PROJECT file: %w\", err)\n\t}\n\treturn projectConfigFile, nil\n}\n\n// Define the version of the binary to be downloaded\nfunc (opts *Update) defineFromVersion() error {\n\t// Allow override of the version from PROJECT file via command line flag\n\tif opts.FromVersion != \"\" {\n\t\tif !semver.IsValid(opts.FromVersion) {\n\t\t\treturn fmt.Errorf(\"invalid semantic version. Expect: vX.Y.Z (Ex: v4.5.0)\")\n\t\t}\n\t\topts.CliVersion = opts.FromVersion\n\t}\n\n\tif opts.CliVersion == \"\" {\n\t\treturn fmt.Errorf(\"failed to retrieve Kubebuilder version from PROJECT file. Please use --from-version to inform it\")\n\t}\n\n\treturn nil\n}\n\n// Validate if the version specified is available as a binary for download\n// from the releases\nfunc (opts *Update) validateBinaryAvailability() error {\n\t// Ensure version has 'v' prefix for consistency with GitHub releases\n\tif !strings.HasPrefix(opts.CliVersion, \"v\") {\n\t\topts.CliVersion = \"v\" + opts.CliVersion\n\t}\n\n\t// Construct the URL for pulling the binary from GitHub releases\n\topts.BinaryURL = fmt.Sprintf(\"https://github.com/kubernetes-sigs/kubebuilder/releases/download/%s/kubebuilder_%s_%s\",\n\t\topts.CliVersion, runtime.GOOS, runtime.GOARCH)\n\n\tresp, err := http.Head(opts.BinaryURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check binary availability: %w\", err)\n\t}\n\tdefer func() {\n\t\tif err = resp.Body.Close(); err != nil {\n\t\t\tlog.Error(\"failed to close connection\", \"error\", err)\n\t\t}\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tlog.Info(\"Binary version available\", \"version\", opts.CliVersion)\n\t\treturn nil\n\tcase http.StatusNotFound:\n\t\treturn fmt.Errorf(\"binary version %s not found. Check versions available in releases\",\n\t\t\topts.CliVersion)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected response %d when checking binary availability for version %s\",\n\t\t\tresp.StatusCode, opts.CliVersion)\n\t}\n}\n\n// Validate if in a git repository with clean state\nfunc (opts *Update) validateGitRepo() error {\n\t// Check if in a git repository\n\tgitCmd := exec.Command(\"git\", \"rev-parse\", \"--git-dir\")\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"not in a git repository\")\n\t}\n\n\t// Check if the branch has uncommitted changes\n\tgitCmd = exec.Command(\"git\", \"status\", \"--porcelain\")\n\toutput, err := gitCmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check branch status: %w\", err)\n\t}\n\n\tif len(strings.TrimSpace(string(output))) > 0 {\n\t\treturn fmt.Errorf(\"working directory has uncommitted changes. Please commit or stash them before updating\")\n\t}\n\n\treturn nil\n}\n\n// Validate the branch passed to the --from-branch flag\nfunc (opts *Update) validateFromBranch() error {\n\t// Set default if not specified\n\tif opts.FromBranch == \"\" {\n\t\topts.FromBranch = \"main\"\n\t}\n\n\t// Check if the branch exists\n\tgitCmd := exec.Command(\"git\", \"rev-parse\", \"--verify\", opts.FromBranch)\n\tif err := gitCmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"%s branch does not exist locally. Run 'git branch -a' to see all available branches\",\n\t\t\topts.FromBranch)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/alpha/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//lint:ignore ST1001 we use dot-imports in tests for brevity\n\npackage alpha\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\n// Figuring out ways to test run these tests similar to existing.\n// Currently unable to run without this on VSCode. Will remove once done\nfunc TestCommand(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"suite test for alpha commands\")\n}\n"
  },
  {
    "path": "internal/cli/alpha/update.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage alpha\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha/internal/update\"\n)\n\n// NewUpdateCommand creates and returns a new Cobra command for updating Kubebuilder projects.\nfunc NewUpdateCommand() *cobra.Command {\n\topts := update.Update{}\n\tvar gitCfg []string\n\tupdateCmd := &cobra.Command{\n\t\tUse:   \"update\",\n\t\tShort: \"Update your project to a newer version (3-way merge; squash by default)\",\n\t\tLong: `Upgrade your project scaffold using a 3-way merge while preserving your code.\n\nThe updater uses four temporary branches during the run:\n  • ancestor : clean scaffold from the starting version (--from-version)\n  • original : snapshot of your current project (--from-branch)\n  • upgrade  : scaffold generated with the target version (--to-version)\n  • merge    : result of merging original into upgrade (conflicts possible)\n\nOutput branch & history:\n  • Default: SQUASH the merge result into ONE commit on:\n        kubebuilder-update-from-<from-version>-to-<to-version>\n  • --show-commits: keep full history (not compatible with --restore-path).\n\nConflicts:\n  • Default: stop on conflicts and leave the merge branch for manual resolution.\n  • --force: commit with conflict markers so automation can proceed.\n\nOther options:\n  • --restore-path: restore paths from base when squashing (e.g., CI configs).\n  • --output-branch: override the output branch name.\n  • --merge-message: custom commit message for clean merges.\n  • --conflict-message: custom commit message for merges with conflicts.\n  • --push: push the output branch to 'origin' after the update.\n  • --git-config: pass per-invocation Git config as -c key=value (repeatable). When not set,\n      defaults are set to improve detection during merges.\n\nDefaults:\n  • --from-version / --to-version: resolved from PROJECT and the latest release if unset.\n  • --from-branch: defaults to 'main' if not specified.`,\n\t\tExample: `\n  # Update from the version in PROJECT to the latest, stop on conflicts\n  kubebuilder alpha update\n\n  # Update from a specific version to latest\n  kubebuilder alpha update --from-version v4.6.0\n\n  # Update from v4.5.0 to v4.7.0 and keep conflict markers (automation-friendly)\n  kubebuilder alpha update --from-version v4.5.0 --to-version v4.7.0 --force\n\n  # Keep full commit history instead of squashing\n  kubebuilder alpha update --from-version v4.5.0 --to-version v4.7.0 --force --show-commits\n\n  # Squash while preserving CI workflows from base (e.g., main)\n  kubebuilder alpha update --force --restore-path .github/workflows\n\n  # Show commits into a custom output branch name\n  kubebuilder alpha update --force --show-commits --output-branch my-update-branch\n\n  # Run update and push the output branch to origin (works with or without --show-commits)\n  kubebuilder alpha update --from-version v4.6.0 --to-version v4.7.0 --force --push\n\n  # Use custom commit messages for both scenarios\n  kubebuilder alpha update --force \\\n    --merge-message \"chore: upgrade kubebuilder scaffold\" \\\n    --conflict-message \"chore: upgrade with conflicts - manual review needed\"\n\n  # Create an issue and add an AI overview comment\n  kubebuilder alpha update --open-gh-issue --use-gh-models\n\n  # Add extra Git configs (no need to re-specify defaults)\n  kubebuilder alpha update --git-config merge.conflictStyle=diff3 --git-config rerere.enabled=true\n                                          \n  # Disable Git config defaults completely, use only custom configs\n  kubebuilder alpha update --git-config disable --git-config rerere.enabled=true`,\n\t\tPreRunE: func(_ *cobra.Command, _ []string) error {\n\t\t\tif opts.ShowCommits && len(opts.RestorePath) > 0 {\n\t\t\t\treturn fmt.Errorf(\"the --restore-path flag is not supported with --show-commits\")\n\t\t\t}\n\n\t\t\tif opts.UseGhModels && !opts.OpenGhIssue {\n\t\t\t\treturn fmt.Errorf(\"the --use-gh-models requires --open-gh-issue to be set\")\n\t\t\t}\n\n\t\t\t// Defaults always on unless \"disable\" is present anywhere\n\t\t\tdefaults := []string{\n\t\t\t\t\"merge.renameLimit=999999\",\n\t\t\t\t\"diff.renameLimit=999999\",\n\t\t\t\t\"merge.conflictStyle=merge\",\n\t\t\t}\n\n\t\t\thasDisable := false\n\t\t\tfiltered := make([]string, 0, len(gitCfg))\n\t\t\tfor _, v := range gitCfg {\n\t\t\t\tif v == \"disable\" {\n\t\t\t\t\thasDisable = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfiltered = append(filtered, v)\n\t\t\t}\n\n\t\t\tif hasDisable {\n\t\t\t\t// no defaults; only user-provided configs (excluding \"disable\")\n\t\t\t\topts.GitConfig = filtered\n\t\t\t} else {\n\t\t\t\t// defaults + user configs (user can override by repeating keys)\n\t\t\t\topts.GitConfig = append(defaults, filtered...)\n\t\t\t}\n\n\t\t\tif err := opts.Prepare(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to prepare update: %w\", err)\n\t\t\t}\n\t\t\treturn opts.Validate()\n\t\t},\n\t\tRun: func(_ *cobra.Command, _ []string) {\n\t\t\tif err := opts.Update(); err != nil {\n\t\t\t\tslog.Error(\"Update failed\", \"error\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t},\n\t}\n\n\tupdateCmd.Flags().StringVar(&opts.FromVersion, \"from-version\", \"\",\n\t\t\"binary release version to upgrade from. Should match the version used to init the project and be \"+\n\t\t\t\"a valid release version, e.g., v4.6.0. If not set, it defaults to the version specified in the PROJECT file.\")\n\tupdateCmd.Flags().StringVar(&opts.ToVersion, \"to-version\", \"\",\n\t\t\"binary release version to upgrade to. Should be a valid release version, e.g., v4.7.0. \"+\n\t\t\t\"If not set, it defaults to the latest release version available in the project repository.\")\n\tupdateCmd.Flags().StringVar(&opts.FromBranch, \"from-branch\", \"\",\n\t\t\"Git branch to use as current state of the project for the update.\")\n\tupdateCmd.Flags().BoolVar(&opts.Force, \"force\", false,\n\t\t\"Force the update even if conflicts occur. Conflicted files will include conflict markers, and a \"+\n\t\t\t\"commit will be created automatically. Ideal for automation (e.g., cronjobs, CI).\")\n\tupdateCmd.Flags().BoolVar(&opts.ShowCommits, \"show-commits\", false,\n\t\t\"If set, the update will keep the full history instead of squashing into a single commit.\")\n\tupdateCmd.Flags().StringArrayVar(&opts.RestorePath, \"restore-path\", nil,\n\t\t\"Paths to preserve from the base branch (repeatable). Not supported with --show-commits.\")\n\tupdateCmd.Flags().StringVar(&opts.OutputBranch, \"output-branch\", \"\",\n\t\t\"Override the default output branch name (default: kubebuilder-update-from-<from-version>-to-<to-version>).\")\n\tupdateCmd.Flags().BoolVar(&opts.Push, \"push\", false,\n\t\t\"Push the output branch to the remote repository after the update.\")\n\tupdateCmd.Flags().StringVar(&opts.CommitMessage, \"merge-message\", \"\",\n\t\t\"Custom commit message for successful merges (no conflicts). \"+\n\t\t\t\"Defaults to 'chore(kubebuilder): update scaffold <from> -> <to>'.\")\n\tupdateCmd.Flags().StringVar(&opts.CommitMessageConflict, \"conflict-message\", \"\",\n\t\t\"Custom commit message for merges with conflicts. \"+\n\t\t\t\"Defaults to 'chore(kubebuilder): (:warning: manual conflict resolution required) update scaffold <from> -> <to>'.\")\n\tupdateCmd.Flags().BoolVar(&opts.OpenGhIssue, \"open-gh-issue\", false,\n\t\t\"Create a GitHub issue with a pre-filled checklist and compare link after the update completes (requires `gh`).\")\n\tupdateCmd.Flags().BoolVar(\n\t\t&opts.UseGhModels,\n\t\t\"use-gh-models\",\n\t\tfalse,\n\t\t\"Generate and post an AI summary comment to the GitHub Issue using `gh models run`. \"+\n\t\t\t\"Requires --open-gh-issue and GitHub CLI (`gh`) with the `gh-models` extension.\")\n\tupdateCmd.Flags().StringArrayVar(\n\t\t&gitCfg,\n\t\t\"git-config\",\n\t\tnil,\n\t\t\"Per-invocation Git config (repeatable). \"+\n\t\t\t\"Defaults: -c merge.renameLimit=999999 -c diff.renameLimit=999999 -c merge.conflictStyle=merge. \"+\n\t\t\t\"Your configs are applied on top. To disable defaults, include `--git-config disable`\")\n\treturn updateCmd\n}\n"
  },
  {
    "path": "internal/cli/alpha/update_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage alpha\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"NewUpdateCommand\", func() {\n\tWhen(\"NewUpdateCommand\", func() {\n\t\tIt(\"Testing the NewUpdateCommand\", func() {\n\t\t\tcmd := NewUpdateCommand()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"update\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Update your project to a newer version\"))\n\n\t\t\tflags := cmd.Flags()\n\t\t\tExpect(flags.Lookup(\"from-version\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"to-version\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"from-branch\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"force\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"show-commits\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"restore-path\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"output-branch\")).NotTo(BeNil())\n\t\t\tExpect(flags.Lookup(\"push\")).NotTo(BeNil())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/cli/cmd/cmd.go",
    "content": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/version\"\n\t\"sigs.k8s.io/kubebuilder/v4/internal/logging\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/cli\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\tkustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\tdeployimagev1alpha1 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\"\n\tgolangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n\tautoupdatev1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha\"\n\tgrafanav1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha\"\n\thelmv1alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha\"\n\thelmv2alpha \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha\"\n)\n\n// Run bootstraps & runs the CLI\nfunc Run() {\n\t// Initialize custom logging handler FIRST - applies to ALL CLI operations\n\topts := logging.HandlerOptions{\n\t\tSlogOpts: slog.HandlerOptions{\n\t\t\tLevel: slog.LevelInfo,\n\t\t},\n\t}\n\thandler := logging.NewHandler(os.Stdout, opts)\n\tlogger := slog.New(handler)\n\tslog.SetDefault(logger)\n\t// Bundle plugin which built the golang projects scaffold with base.go/v4 and kustomize/v2 plugins\n\tgov4Bundle, _ := plugin.NewBundleWithOptions(plugin.WithName(golang.DefaultNameQualifier),\n\t\tplugin.WithVersion(plugin.Version{Number: 4}),\n\t\tplugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv4.Plugin{}),\n\t\tplugin.WithDescription(\"Default scaffold (go/v4 + kustomize/v2)\"),\n\t)\n\n\tfs := machinery.Filesystem{\n\t\tFS: afero.NewOsFs(),\n\t}\n\texternalPlugins, err := cli.DiscoverExternalPlugins(fs.FS)\n\tif err != nil {\n\t\tslog.Error(\"error discovering external plugins\", \"error\", err)\n\t}\n\n\tv := version.New()\n\tc, err := cli.New(\n\t\tcli.WithCommandName(\"kubebuilder\"),\n\t\tcli.WithVersion(v.PrintVersion()),\n\t\tcli.WithCliVersion(v.GetKubeBuilderVersion()),\n\t\tcli.WithPlugins(\n\t\t\tgolangv4.Plugin{},\n\t\t\tgov4Bundle,\n\t\t\t&kustomizecommonv2.Plugin{},\n\t\t\t&deployimagev1alpha1.Plugin{},\n\t\t\t&grafanav1alpha.Plugin{},\n\t\t\t&helmv1alpha.Plugin{},\n\t\t\t&helmv2alpha.Plugin{},\n\t\t\t&autoupdatev1alpha.Plugin{},\n\t\t),\n\t\tcli.WithPlugins(externalPlugins...),\n\t\tcli.WithDefaultPlugins(cfgv3.Version, gov4Bundle),\n\t\tcli.WithDefaultProjectVersion(cfgv3.Version),\n\t\tcli.WithCompletion(),\n\t)\n\tif err != nil {\n\t\tslog.Error(\"failed to create CLI\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n\tif err := c.Run(); err != nil {\n\t\tslog.Error(\"CLI run failed\", \"error\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "internal/cli/version/version.go",
    "content": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n)\n\nconst (\n\tunknown                 = \"unknown\"\n\tdevelVersion            = \"(devel)\"\n\tkubernetesVendorVersion = \"1.35.0\"\n)\n\ntype Version struct {\n\tKubeBuilderVersion string `json:\"kubeBuilderVersion\"`\n\tKubernetesVendor   string `json:\"kubernetesVendor\"`\n\tGitCommit          string `json:\"gitCommit\"`\n\tBuildDate          string `json:\"buildDate\"`\n\tGoOs               string `json:\"goOs\"`\n\tGoArch             string `json:\"goArch\"`\n}\n\nfunc New() Version {\n\tv := Version{\n\t\tKubeBuilderVersion: develVersion,\n\t\tKubernetesVendor:   kubernetesVendorVersion,\n\t\tGitCommit:          unknown,\n\t\tBuildDate:          unknown,\n\t\tGoOs:               runtime.GOOS,\n\t\tGoArch:             runtime.GOARCH,\n\t}\n\n\tif info, ok := debug.ReadBuildInfo(); ok {\n\t\tv.KubeBuilderVersion = resolveMainVersion(info.Main)\n\t\tv.applyVCSMetadata(info.Settings)\n\t}\n\n\tif testVersion := os.Getenv(\"KUBEBUILDER_TEST_VERSION\"); testVersion != \"\" {\n\t\tv.KubeBuilderVersion = testVersion\n\t\tv.GitCommit = \"test-commit\"\n\t\tv.BuildDate = \"1970-01-01T00:00:00Z\"\n\t}\n\n\treturn v\n}\n\n// GetKubeBuilderVersion returns only the CLI version string.\n// Used for the cliVersion field in scaffolded PROJECT files.\nfunc (v Version) GetKubeBuilderVersion() string {\n\treturn strings.TrimPrefix(v.KubeBuilderVersion, \"v\")\n}\n\nfunc resolveMainVersion(main debug.Module) string {\n\tif main.Version != \"\" {\n\t\treturn main.Version\n\t}\n\treturn develVersion\n}\n\n// isPseudoVersion reports whether a version is a pseudo-version\n// (e.g., v0.0.0-20191109021931-daa7c04131f5 or v1.2.4-0.20191109021931-daa7c04131f5)\nfunc isPseudoVersion(v string) bool {\n\treturn strings.Contains(v, \"-0.\")\n}\n\nfunc (v *Version) applyVCSMetadata(settings []debug.BuildSetting) {\n\tvar isDirty bool\n\n\tfor _, s := range settings {\n\t\tswitch s.Key {\n\t\tcase \"vcs.revision\":\n\t\t\tv.GitCommit = s.Value\n\t\tcase \"vcs.time\":\n\t\t\tv.BuildDate = s.Value\n\t\tcase \"vcs.modified\":\n\t\t\tisDirty = (s.Value == \"true\")\n\t\t}\n\t}\n\n\tif isDirty {\n\t\t// For development builds (not proper releases), use develVersion to avoid\n\t\t// polluting PROJECT files with unstable -dirty version strings.\n\t\t// For tagged releases, ignore the dirty flag to support GoReleaser builds\n\t\t// that may create artifacts during the build process.\n\t\tif v.KubeBuilderVersion == develVersion || isPseudoVersion(v.KubeBuilderVersion) {\n\t\t\tv.KubeBuilderVersion = develVersion\n\t\t}\n\t\t// Note: We don't append -dirty to tagged release versions to support\n\t\t// GoReleaser and similar build tools that may modify files during build.\n\n\t\tif !strings.Contains(v.GitCommit, \"dirty\") {\n\t\t\tv.GitCommit += \"-dirty\"\n\t\t}\n\t}\n}\n\nfunc (v Version) PrintVersion() string {\n\treturn fmt.Sprintf(`KubeBuilder:          %s\nKubernetes:           %s\nGit Commit:           %s\nBuild Date:           %s\nGo OS/Arch:           %s/%s`,\n\t\tv.KubeBuilderVersion,\n\t\tv.KubernetesVendor,\n\t\tv.GitCommit,\n\t\tv.BuildDate,\n\t\tv.GoOs,\n\t\tv.GoArch,\n\t)\n}\n"
  },
  {
    "path": "internal/cli/version/version_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage version\n\nimport (\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestNew(t *testing.T) {\n\tt.Run(\"Environment variable override\", func(t *testing.T) {\n\t\texpectedVersion := \"v9.9.9-test\"\n\t\tt.Setenv(\"KUBEBUILDER_TEST_VERSION\", expectedVersion)\n\n\t\tv := New()\n\n\t\tif v.KubeBuilderVersion != expectedVersion {\n\t\t\tt.Errorf(\"expected version %s, got %s\", expectedVersion, v.KubeBuilderVersion)\n\t\t}\n\t\tif v.GitCommit != \"test-commit\" {\n\t\t\tt.Errorf(\"expected gitCommit 'test-commit', got %s\", v.GitCommit)\n\t\t}\n\t})\n\n\tt.Run(\"Fallback behavior\", func(t *testing.T) {\n\t\tv := New()\n\n\t\tif v.KubernetesVendor != kubernetesVendorVersion {\n\t\t\tt.Errorf(\"expected vendor %s, got %s\", kubernetesVendorVersion, v.KubernetesVendor)\n\t\t}\n\t\tif v.GoOs == \"\" || v.GoArch == \"\" {\n\t\t\tt.Error(\"GoOs or GoArch was not populated from runtime\")\n\t\t}\n\t})\n\n\tt.Run(\"VCS metadata resolution with tagged release\", func(t *testing.T) {\n\t\tv := &Version{KubeBuilderVersion: \"v1.0.0\"}\n\t\tsettings := []debug.BuildSetting{\n\t\t\t{Key: \"vcs.revision\", Value: \"abcdef123\"},\n\t\t\t{Key: \"vcs.modified\", Value: \"true\"},\n\t\t}\n\n\t\tv.applyVCSMetadata(settings)\n\n\t\tif !strings.HasSuffix(v.GitCommit, \"-dirty\") {\n\t\t\tt.Errorf(\"expected commit to be dirty, got %s\", v.GitCommit)\n\t\t}\n\t\t// For tagged releases, we ignore dirty flag to support GoReleaser builds\n\t\tif v.KubeBuilderVersion != \"v1.0.0\" {\n\t\t\tt.Errorf(\"expected version to remain v1.0.0, got %s\", v.KubeBuilderVersion)\n\t\t}\n\t})\n\n\tt.Run(\"Version string formatting\", func(t *testing.T) {\n\t\tv := Version{KubeBuilderVersion: \"v1.2.3\"}\n\n\t\tcleanVersion := v.GetKubeBuilderVersion()\n\t\tif cleanVersion != \"1.2.3\" {\n\t\t\tt.Errorf(\"expected 1.2.3, got %s\", cleanVersion)\n\t\t}\n\t})\n}\n\nfunc TestGetKubeBuilderVersion(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\"strips v prefix\", \"v1.35.0\", \"1.35.0\"},\n\t\t{\"handles no prefix\", \"1.35.0\", \"1.35.0\"},\n\t\t{\"preserves devel\", \"(devel)\", \"(devel)\"},\n\t\t{\"handles empty\", \"\", \"\"},\n\t\t{\"handles dirty suffix\", \"v1.35.0-dirty\", \"1.35.0-dirty\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tv := Version{KubeBuilderVersion: tc.input}\n\t\t\tif actual := v.GetKubeBuilderVersion(); actual != tc.expected {\n\t\t\t\tt.Errorf(\"GetKubeBuilderVersion(%s) = %s; want %s\", tc.input, actual, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResolveMainVersion(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\"Valid Tag\", \"v4.10.4\", \"v4.10.4\"},\n\t\t{\"Development Build\", \"\", develVersion},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tmain := debug.Module{Version: tt.input}\n\t\t\tif got := resolveMainVersion(main); got != tt.expected {\n\t\t\t\tt.Errorf(\"resolveMainVersion() = %v, want %v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestApplyVCSMetadata(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tinitialVersion string\n\t\tsettings       []debug.BuildSetting\n\t\texpectCommit   string\n\t\texpectVersion  string\n\t\texpectDate     string\n\t}{\n\t\t{\n\t\t\tname:           \"Clean release build\",\n\t\t\tinitialVersion: \"v4.10.4\",\n\t\t\tsettings: []debug.BuildSetting{\n\t\t\t\t{Key: \"vcs.revision\", Value: \"abcdef123\"},\n\t\t\t\t{Key: \"vcs.time\", Value: \"2025-12-27T18:00:00Z\"},\n\t\t\t\t{Key: \"vcs.modified\", Value: \"false\"},\n\t\t\t},\n\t\t\texpectCommit:  \"abcdef123\",\n\t\t\texpectVersion: \"v4.10.4\",\n\t\t\texpectDate:    \"2025-12-27T18:00:00Z\",\n\t\t},\n\t\t{\n\t\t\tname:           \"Dirty development build\",\n\t\t\tinitialVersion: develVersion,\n\t\t\tsettings: []debug.BuildSetting{\n\t\t\t\t{Key: \"vcs.revision\", Value: \"abcdef123\"},\n\t\t\t\t{Key: \"vcs.modified\", Value: \"true\"},\n\t\t\t\t{Key: \"vcs.time\", Value: \"2025-12-29T19:30:00Z\"},\n\t\t\t},\n\t\t\texpectCommit:  \"abcdef123-dirty\",\n\t\t\texpectVersion: \"(devel)\",\n\t\t\texpectDate:    \"2025-12-29T19:30:00Z\",\n\t\t},\n\t\t{\n\t\t\tname:           \"Dirty tagged release (GoReleaser scenario)\",\n\t\t\tinitialVersion: \"v4.5.3-rc.1\",\n\t\t\tsettings: []debug.BuildSetting{\n\t\t\t\t{Key: \"vcs.revision\", Value: \"abcdef123\"},\n\t\t\t\t{Key: \"vcs.modified\", Value: \"true\"},\n\t\t\t\t{Key: \"vcs.time\", Value: \"2025-12-30T10:00:00Z\"},\n\t\t\t},\n\t\t\texpectCommit:  \"abcdef123-dirty\",\n\t\t\texpectVersion: \"v4.5.3-rc.1\", // Stays clean for tagged releases\n\t\t\texpectDate:    \"2025-12-30T10:00:00Z\",\n\t\t},\n\t\t{\n\t\t\tname:           \"Dirty pseudo-version\",\n\t\t\tinitialVersion: \"v1.2.4-0.20191109021931-daa7c04131f5\",\n\t\t\tsettings: []debug.BuildSetting{\n\t\t\t\t{Key: \"vcs.revision\", Value: \"abcdef123\"},\n\t\t\t\t{Key: \"vcs.modified\", Value: \"true\"},\n\t\t\t},\n\t\t\texpectCommit:  \"abcdef123-dirty\",\n\t\t\texpectVersion: \"(devel)\", // Pseudo-versions become (devel) when dirty\n\t\t\texpectDate:    \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tv := &Version{\n\t\t\t\tKubeBuilderVersion: tt.initialVersion,\n\t\t\t}\n\t\t\tv.applyVCSMetadata(tt.settings)\n\n\t\t\tif v.GitCommit != tt.expectCommit {\n\t\t\t\tt.Errorf(\"GitCommit = %v, want %v\", v.GitCommit, tt.expectCommit)\n\t\t\t}\n\t\t\tif v.KubeBuilderVersion != tt.expectVersion {\n\t\t\t\tt.Errorf(\"KubeBuilderVersion = %v, want %v\", v.KubeBuilderVersion, tt.expectVersion)\n\t\t\t}\n\t\t\tif v.BuildDate != tt.expectDate {\n\t\t\t\tt.Errorf(\"BuildDate = %v, want %v\", v.BuildDate, tt.expectDate)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPrintVersion(t *testing.T) {\n\tv := Version{\n\t\tKubeBuilderVersion: \"v9.99.9\",\n\t\tKubernetesVendor:   \"9.99.9\",\n\t\tGitCommit:          \"9990f08847dd1\",\n\t\tBuildDate:          \"1970-01-12T12:12:12Z\",\n\t\tGoOs:               \"linux\",\n\t\tGoArch:             \"amd64\",\n\t}\n\n\texpectedOutput := `KubeBuilder:          v9.99.9\nKubernetes:           9.99.9\nGit Commit:           9990f08847dd1\nBuild Date:           1970-01-12T12:12:12Z\nGo OS/Arch:           linux/amd64`\n\n\tactualOutput := v.PrintVersion()\n\n\tif actualOutput != expectedOutput {\n\t\tt.Errorf(\"different output in version subcommand.\\nexpected:\\n%v\\ngot:\\n%v\",\n\t\t\texpectedOutput, actualOutput)\n\t}\n}\n"
  },
  {
    "path": "internal/logging/handler.go",
    "content": "/*\nCopyright 2025 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage logging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"log/slog\"\n)\n\nconst (\n\tColorReset = \"\\033[0m\"\n\tColorError = \"\\033[31m\"\n\tColorWarn  = \"\\033[33m\"\n\tColorInfo  = \"\\033[36m\"\n\tColorDebug = \"\\033[32m\"\n)\n\ntype HandlerOptions struct {\n\tSlogOpts slog.HandlerOptions\n}\n\ntype Handler struct {\n\tslog.Handler\n\tl *log.Logger\n}\n\nfunc (h *Handler) Enabled(ctx context.Context, level slog.Level) bool {\n\treturn h.Handler.Enabled(ctx, level)\n}\n\nfunc (h *Handler) Handle(_ context.Context, r slog.Record) error {\n\tvar color string\n\tswitch r.Level {\n\tcase slog.LevelDebug:\n\t\tcolor = ColorDebug + r.Level.String() + ColorReset\n\tcase slog.LevelInfo:\n\t\tcolor = ColorInfo + r.Level.String() + ColorReset\n\tcase slog.LevelWarn:\n\t\tcolor = ColorWarn + r.Level.String() + ColorReset\n\tcase slog.LevelError:\n\t\tcolor = ColorError + r.Level.String() + ColorReset\n\t}\n\tattrs := \"\"\n\tr.Attrs(func(attr slog.Attr) bool {\n\t\tattrs += fmt.Sprintf(\"%s=%v\", attr.Key, attr.Value.Any())\n\t\treturn true\n\t})\n\n\th.l.Println(color, r.Message, attrs)\n\treturn nil\n}\n\nfunc (h *Handler) WithAttrs(_ []slog.Attr) slog.Handler {\n\treturn h\n}\n\nfunc (h *Handler) WithGroup(_ string) slog.Handler {\n\treturn h\n}\n\nfunc NewHandler(out io.Writer, opts HandlerOptions) *Handler {\n\th := &Handler{\n\t\tHandler: slog.NewTextHandler(out, &opts.SlogOpts),\n\t\tl:       log.New(out, \"\", 0),\n\t}\n\n\treturn h\n}\n"
  },
  {
    "path": "main.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport \"sigs.k8s.io/kubebuilder/v4/internal/cli/cmd\"\n\nfunc main() {\n\tcmd.Run()\n}\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n    base = \"docs/book\"\n    command = \"GO_VERSION=1.25.0 ./install-and-build.sh\"\n    publish = \"docs/book/book\"\n    functions = \"docs/book/functions\"\n\n# Disables Pretty URLs feature so it doesn't break mdBook's ToC logic\n[build.processing.html]\n    pretty_urls = false\n\n# TODO(directxman12): I don't know why, but this (functions) stanza is in the\n# docs and local `netlify dev`, but the above one (under build) is used by the\n# online version :-/\n\n# used to handle the split between v2 and v3+ download links\n[functions]\n    # relative to base directory\n    directory = \"functions\"\n\n# Standard Netlify redirects\n[[redirects]]\n    from = \"https://kubebuilder.netlify.com/*\"\n    to = \"https://book.kubebuilder.io/:splat\"\n    status = 301\n    force = true\n\n# HTTP-to-HTTPS rules\n[[redirects]]\n    from = \"http://go.kubebuilder.io/*\"\n    to = \"https://go.kubebuilder.io/:splat\"\n    status = 301\n    force = true\n\n[[redirects]]\n    from = \"http://kubebuilder.netlify.com/*\"\n    to = \"http://book.kubebuilder.io/:splat\"\n    status = 301\n    force = true\n\n# kubebuilder binary (v3+) and tarball (< v3) redirects.\n[[redirects]]\n    from = \"https://go.kubebuilder.io/dl/*\"\n    to = \"https://go.kubebuilder.io/releases/:splat\"\n    status = 301\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases\"\n    to = \"https://github.com/kubernetes-sigs/kubebuilder/releases\"\n    status = 302\n    force = true\n\n# Development branch redirect.\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/master/:os/:arch\"\n    to = \"https://storage.googleapis.com/kubebuilder-release/kubebuilder_master_:os_:arch.tar.gz\"\n    status = 302\n    force = true\n\n# Latest redirects.\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/latest\"\n    to = \"https://github.com/kubernetes-sigs/kubebuilder/releases/latest\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/latest/:os\"\n    to = \"https://go.kubebuilder.io/releases/latest/:os/amd64\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/latest/:os/:arch\"\n    to = \"https://github.com/kubernetes-sigs/kubebuilder/releases/latest/download/kubebuilder_:os_:arch\"\n    status = 302\n    force = true\n\n# general release redirects\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/:version\"\n    to = \"https://github.com/kubernetes-sigs/kubebuilder/releases/v:version\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/:version/:os\"\n    to = \"https://go.kubebuilder.io/releases/:version/:os/amd64\"\n    status = 302\n    force = true\n\n# release download redirect\n[[redirects]]\n    from = \"https://go.kubebuilder.io/releases/:version/:os/:arch\"\n    # I don't quite know why, but netlify (or at least the dev mode) *insists*\n    # on eating every other query parameter, so just use paths instead\n    to = \"/.netlify/functions/handle-version/releases/:version/:os/:arch\"\n    # 200 --> don't redirect to the function then to whereever it says,\n    # just pretend like the function is mounted directly here\n    status = 200\n    force = true\n\n# Tools redirects.\n[[redirects]]\n    from = \"https://go.kubebuilder.io/test-tools\"\n    to = \"https://storage.googleapis.com/kubebuilder-tools\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/test-tools/:k8sversion\"\n    to = \"https://storage.googleapis.com/kubebuilder-tools/?prefix=kubebuilder-tools-:k8sversion\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/test-tools/:k8sversion/:os\"\n    to = \"https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-:k8sversion-:os-amd64.tar.gz\"\n    status = 302\n    force = true\n\n[[redirects]]\n    from = \"https://go.kubebuilder.io/test-tools/:k8sversion/:os/:arch\"\n    to = \"https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-:k8sversion-:os-:arch.tar.gz\"\n    status = 302\n    force = true\n\n# custom 404 handling -- this may need to be last -- netlify docs are unclear\n[[redirects]]\n    from = \"/*\"\n    to = \"/404.html\"\n    status = 404\n"
  },
  {
    "path": "pkg/cli/alpha.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/internal/cli/alpha\"\n)\n\nconst (\n\talphaCommand = \"alpha\"\n)\n\nvar alphaCommands = []*cobra.Command{\n\tnewAlphaCommand(),\n\talpha.NewScaffoldCommand(),\n\talpha.NewUpdateCommand(),\n}\n\nfunc newAlphaCommand() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\t// TODO: If we need to create alpha commands please add a new file for each command\n\t}\n\treturn cmd\n}\n\nfunc (c *CLI) newAlphaCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:        alphaCommand,\n\t\tSuggestFor: []string{\"experimental\"},\n\t\tShort:      \"Alpha-stage subcommands\",\n\t\tLong: strings.TrimSpace(`\nAlpha subcommands are for unstable features.\n\n- Alpha subcommands are exploratory and may be removed without warning.\n- No backwards compatibility is provided for any alpha subcommands.\n`),\n\t}\n\t// TODO: Add alpha commands here if we need to have them\n\tfor i := range alphaCommands {\n\t\tcmd.AddCommand(alphaCommands[i])\n\t}\n\treturn cmd\n}\n\nfunc (c *CLI) addAlphaCmd() {\n\tif (len(alphaCommands) + len(c.extraAlphaCommands)) > 0 {\n\t\tc.cmd.AddCommand(c.newAlphaCmd())\n\t}\n}\n\nfunc (c *CLI) addExtraAlphaCommands() error {\n\t// Search for the alpha subcommand\n\tvar cmds *cobra.Command\n\tfor _, subCmd := range c.cmd.Commands() {\n\t\tif subCmd.Name() == alphaCommand {\n\t\t\tcmds = subCmd\n\t\t\tbreak\n\t\t}\n\t}\n\tif cmds == nil {\n\t\treturn fmt.Errorf(\"no %q command found\", alphaCommand)\n\t}\n\n\tfor _, cmd := range c.extraAlphaCommands {\n\t\tfor _, subCmd := range cmds.Commands() {\n\t\t\tif cmd.Name() == subCmd.Name() {\n\t\t\t\treturn fmt.Errorf(\"command %q already exists\", fmt.Sprintf(\"%s %s\", alphaCommand, cmd.Name()))\n\t\t\t}\n\t\t}\n\t\tcmds.AddCommand(cmd)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cli/api.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nconst apiErrorMsg = \"failed to create API\"\n\nfunc (c CLI) newCreateAPICmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"api\",\n\t\tShort: \"Scaffold a Kubernetes API\",\n\t\tLong:  `Scaffold a Kubernetes API.`,\n\t\tRunE: errCmdFunc(\n\t\t\tfmt.Errorf(\"api subcommand requires an existing project\"),\n\t\t),\n\t}\n\n\t// In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of\n\t// this subcommand. This allows the use of subcommands that do not require resolved plugins like help.\n\tif len(c.resolvedPlugins) == 0 {\n\t\tcmdErr(cmd, noResolvedPluginError{})\n\t\treturn cmd\n\t}\n\n\t// Obtain the plugin keys and subcommands from the plugins that implement plugin.CreateAPI.\n\tsubcommands := c.filterSubcommands(\n\t\tfunc(p plugin.Plugin) bool {\n\t\t\t_, isValid := p.(plugin.CreateAPI)\n\t\t\treturn isValid\n\t\t},\n\t\tfunc(p plugin.Plugin) plugin.Subcommand {\n\t\t\treturn p.(plugin.CreateAPI).GetCreateAPISubcommand()\n\t\t},\n\t)\n\n\t// Verify that there is at least one remaining plugin.\n\tif len(subcommands) == 0 {\n\t\tcmdErr(cmd, noAvailablePluginError{\"API creation\"})\n\t\treturn cmd\n\t}\n\n\tc.applySubcommandHooks(cmd, subcommands, apiErrorMsg, false)\n\n\t// Append plugin table after metadata updates\n\tc.appendPluginTable(cmd, func(p plugin.Plugin) bool {\n\t\t_, isValid := p.(plugin.CreateAPI)\n\t\treturn isValid\n\t}, \"Available plugins that support 'create api'\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cli/cli.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tyamlstore \"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nconst (\n\tnoticeColor    = \"\\033[1;33m%s\\033[0m\"\n\tdeprecationFmt = \"[Deprecation Notice] %s\\n\\n\"\n\n\tpluginsFlag        = \"plugins\"\n\tprojectVersionFlag = \"project-version\"\n)\n\n// CLI is the command line utility that is used to scaffold kubebuilder project files.\ntype CLI struct {\n\t/* Fields set by Option */\n\n\t// Root command name. It is injected downstream to provide correct help, usage, examples and errors.\n\tcommandName string\n\t// Full CLI version string.\n\tversion string\n\t// CLI version string (just the CLI version number, no extra information).\n\tcliVersion string\n\t// CLI root's command description.\n\tdescription string\n\t// Plugins registered in the CLI.\n\tplugins map[string]plugin.Plugin\n\t// Default plugins in case none is provided and a config file can't be found.\n\tdefaultPlugins map[config.Version][]string\n\t// Default project version in case none is provided and a config file can't be found.\n\tdefaultProjectVersion config.Version\n\t// Commands injected by options.\n\textraCommands []*cobra.Command\n\t// Alpha commands injected by options.\n\textraAlphaCommands []*cobra.Command\n\t// Whether to add a completion command to the CLI.\n\tcompletionCommand bool\n\n\t/* Internal fields */\n\n\t// Plugin keys to scaffold with.\n\tpluginKeys []string\n\t// Project version to scaffold.\n\tprojectVersion config.Version\n\n\t// A filtered set of plugins that should be used by command constructors.\n\tresolvedPlugins []plugin.Plugin\n\n\t// Root command.\n\tcmd *cobra.Command\n\n\t// Underlying fs\n\tfs machinery.Filesystem\n}\n\n// New creates a new CLI instance.\n//\n// It follows the functional options pattern in order to customize the resulting CLI.\n//\n// It returns an error if any of the provided options fails. As some processing needs\n// to be done, execution errors may be found here. Instead of returning an error, this\n// function will return a valid CLI that errors in Run so that help is provided to the\n// user.\nfunc New(options ...Option) (*CLI, error) {\n\t// Create the CLI.\n\tc, err := newCLI(options...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Build the cmd tree.\n\tif err := c.buildCmd(); err != nil {\n\t\tc.cmd.RunE = errCmdFunc(err)\n\t\treturn c, nil\n\t}\n\n\t// Add extra commands injected by options.\n\tif err := c.addExtraCommands(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add extra alpha commands injected by options.\n\tif err := c.addExtraAlphaCommands(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write deprecation notices after all commands have been constructed.\n\tc.printDeprecationWarnings()\n\n\treturn c, nil\n}\n\n// newCLI creates a default CLI instance and applies the provided options.\n// It is as a separate function for test purposes.\nfunc newCLI(options ...Option) (*CLI, error) {\n\t// Default CLI options.\n\tc := &CLI{\n\t\tcommandName: \"kubebuilder\",\n\t\tdescription: `CLI tool for building Kubernetes extensions and tools.\n`,\n\t\tplugins:        make(map[string]plugin.Plugin),\n\t\tdefaultPlugins: make(map[config.Version][]string),\n\t\tfs:             machinery.Filesystem{FS: afero.NewOsFs()},\n\t}\n\n\t// Apply provided options.\n\tfor _, option := range options {\n\t\tif err := option(c); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn c, nil\n}\n\n// buildCmd creates the underlying cobra command and stores it internally.\nfunc (c *CLI) buildCmd() error {\n\tc.cmd = c.newRootCmd()\n\n\tvar uve config.UnsupportedVersionError\n\n\t// Get project version and plugin keys.\n\tswitch err := c.getInfo(); {\n\tcase err == nil:\n\tcase errors.As(err, &uve) && uve.Version.Compare(config.Version{Number: 3, Stage: stage.Alpha}) == 0:\n\t\t// Check if the corresponding stable version exists, set c.projectVersion and break\n\t\tstableVersion := config.Version{\n\t\t\tNumber: uve.Version.Number,\n\t\t}\n\t\tif config.IsRegistered(stableVersion) {\n\t\t\t// Use the stableVersion\n\t\t\tc.projectVersion = stableVersion\n\t\t} else {\n\t\t\t// stable version not registered, let's bail out\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn err\n\t}\n\n\t// Resolve plugins for project version and plugin keys.\n\tif err := c.resolvePlugins(); err != nil {\n\t\treturn err\n\t}\n\n\t// Add the subcommands\n\tc.addSubcommands()\n\n\treturn nil\n}\n\n// getInfo obtains the plugin keys and project version resolving conflicts between the project config file and flags.\nfunc (c *CLI) getInfo() error {\n\t// Get plugin keys and project version from project configuration file\n\t// We discard the error if file doesn't exist because not being able to read a project configuration\n\t// file is not fatal for some commands. The ones that require it need to check its existence later.\n\thasConfigFile := true\n\tif err := c.getInfoFromConfigFile(); errors.Is(err, os.ErrNotExist) {\n\t\thasConfigFile = false\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\t// We can't early return here in case a project configuration file was found because\n\t// this command call may override the project plugins.\n\n\t// Get project version and plugin info from flags\n\tif err := c.getInfoFromFlags(hasConfigFile); err != nil {\n\t\treturn err\n\t}\n\n\t// Get project version and plugin info from defaults\n\tc.getInfoFromDefaults()\n\n\treturn nil\n}\n\n// getInfoFromConfigFile obtains the project version and plugin keys from the project config file.\nfunc (c *CLI) getInfoFromConfigFile() error {\n\t// Read the project configuration file\n\tcfg := yamlstore.New(c.fs)\n\n\t// Workaround for https://github.com/kubernetes-sigs/kubebuilder/issues/4433\n\t//\n\t// This allows the `kubebuilder alpha generate` command to work with old projects\n\t// that use plugin versions no longer supported (like go.kubebuilder.io/v3).\n\t//\n\t// We read the PROJECT file into memory and update the plugin version (e.g. from v3 to v4)\n\t// before the CLI tries to load it. This avoids errors during config loading\n\t// and lets users migrate their project layout from go/v3 to go/v4.\n\n\tif isAlphaGenerateCommand(os.Args[1:]) {\n\t\t// Patch raw file bytes before unmarshalling\n\t\tif err := patchProjectFileInMemoryIfNeeded(c.fs.FS, yamlstore.DefaultPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := cfg.Load(); err != nil {\n\t\treturn fmt.Errorf(\"error loading configuration: %w\", err)\n\t}\n\n\treturn c.getInfoFromConfig(cfg.Config())\n}\n\n// isAlphaGenerateCommand checks if the command invocation is `kubebuilder alpha generate`\n// by scanning os.Args (excluding global flags). It returns true if \"alpha\" is followed by \"generate\".\nfunc isAlphaGenerateCommand(args []string) bool {\n\tpositional := []string{}\n\tskip := false\n\n\tfor i := range args {\n\t\targ := args[i]\n\n\t\t// Skip flags and their values\n\t\tif strings.HasPrefix(arg, \"-\") {\n\t\t\t// If the flag is in --flag=value format, skip only this one\n\t\t\tif strings.Contains(arg, \"=\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// If it's --flag value format, skip next one too\n\t\t\tif i+1 < len(args) && !strings.HasPrefix(args[i+1], \"-\") {\n\t\t\t\tskip = true\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif skip {\n\t\t\tskip = false\n\t\t\tcontinue\n\t\t}\n\t\tpositional = append(positional, arg)\n\t}\n\n\t// Check for `alpha generate` in positional arguments\n\tfor i := 0; i < len(positional)-1; i++ {\n\t\tif positional[i] == \"alpha\" && positional[i+1] == \"generate\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// patchProjectFileInMemoryIfNeeded updates deprecated plugin keys in the PROJECT file in place,\n// so that users can run `kubebuilder alpha generate` even with older plugin layouts.\n//\n// See: https://github.com/kubernetes-sigs/kubebuilder/issues/4433\n//\n// This ensures the CLI can successfully load the config without failing on unsupported plugin versions.\nfunc patchProjectFileInMemoryIfNeeded(fs afero.Fs, path string) error {\n\ttype pluginReplacement struct {\n\t\tOld string\n\t\tNew string\n\t}\n\n\treplacements := []pluginReplacement{\n\t\t{\"go.kubebuilder.io/v2\", \"go.kubebuilder.io/v4\"},\n\t\t{\"go.kubebuilder.io/v3\", \"go.kubebuilder.io/v4\"},\n\t\t{\"go.kubebuilder.io/v3-alpha\", \"go.kubebuilder.io/v4\"},\n\t}\n\n\tcontent, err := afero.ReadFile(fs, path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\toriginal := string(content)\n\tmodified := original\n\n\tfor _, rep := range replacements {\n\t\tif strings.Contains(modified, rep.Old) {\n\t\t\tmodified = strings.ReplaceAll(modified, rep.Old, rep.New)\n\t\t\tlog.Warn(\"Project is using an old and unsupported plugin layout\",\n\t\t\t\t\"old_layout\", rep.Old,\n\t\t\t\t\"new_layout\", rep.New,\n\t\t\t\t\"note\", \"Replace in memory to allow `alpha generate` to work.\",\n\t\t\t)\n\t\t}\n\t}\n\n\tif modified != original {\n\t\terr := afero.WriteFile(fs, path, []byte(modified), machinery.DefaultFilePermission)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write patched PROJECT file: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// getInfoFromConfig obtains the project version and plugin keys from the project config.\n// It is extracted from getInfoFromConfigFile for testing purposes.\nfunc (c *CLI) getInfoFromConfig(projectConfig config.Config) error {\n\tc.pluginKeys = projectConfig.GetPluginChain()\n\tc.projectVersion = projectConfig.GetVersion()\n\n\tfor _, pluginKey := range c.pluginKeys {\n\t\tif err := plugin.ValidateKey(pluginKey); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid plugin key found in project configuration file: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// getInfoFromFlags obtains the project version and plugin keys from flags.\nfunc (c *CLI) getInfoFromFlags(hasConfigFile bool) error {\n\t// Check if --plugins is followed by --help or -h to avoid parsing help as a plugin value\n\t// This fixes: kubebuilder init --plugins --help\n\tfor i := 0; i < len(os.Args)-1; i++ {\n\t\tif os.Args[i] == \"--plugins\" || os.Args[i] == \"--plugins=\" {\n\t\t\tnextArg := os.Args[i+1]\n\t\t\tif isHelpFlag(nextArg) {\n\t\t\t\t// Help was requested, return early to let Cobra handle it\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\t// Partially parse the command line arguments\n\tfs := pflag.NewFlagSet(\"base\", pflag.ContinueOnError)\n\n\t// Load the base command global flags\n\tfs.AddFlagSet(c.cmd.PersistentFlags())\n\n\t// If we were unable to load the project configuration, we should also accept the project version flag\n\tvar projectVersionStr string\n\tif !hasConfigFile {\n\t\tfs.StringVar(&projectVersionStr, projectVersionFlag, \"\", \"project version\")\n\t}\n\n\t// FlagSet special cases --help and -h, so we need to create a dummy flag with these 2 values to prevent the default\n\t// behavior (printing the usage of this FlagSet) as we want to print the usage message of the underlying command.\n\tfs.BoolP(\"help\", \"h\", false, fmt.Sprintf(\"help for %s\", c.commandName))\n\n\t// Omit unknown flags to avoid parsing errors\n\tfs.ParseErrorsAllowlist = pflag.ParseErrorsAllowlist{UnknownFlags: true}\n\n\t// Parse the arguments\n\tif err := fs.Parse(os.Args[1:]); err != nil {\n\t\treturn fmt.Errorf(\"could not parse flags: %w\", err)\n\t}\n\n\t// If any plugin key was provided, replace those from the project configuration file\n\tif pluginKeys, err := fs.GetStringSlice(pluginsFlag); err != nil {\n\t\treturn fmt.Errorf(\"invalid flag %q: %w\", pluginsFlag, err)\n\t} else if len(pluginKeys) != 0 {\n\t\t// Filter out help flags that may have been incorrectly parsed as plugin values\n\t\t// This fixes the issue where \"kubebuilder edit --plugins --help\" treats --help as a plugin\n\t\tvalidPluginKeys := make([]string, 0, len(pluginKeys))\n\t\thelpRequested := false\n\t\tfor _, key := range pluginKeys {\n\t\t\tkey = strings.TrimSpace(key)\n\t\t\t// Skip help flags\n\t\t\tif isHelpFlag(key) {\n\t\t\t\thelpRequested = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvalidPluginKeys = append(validPluginKeys, key)\n\t\t}\n\n\t\t// If help was requested via --plugins flag, set the help flag to trigger Cobra's help display\n\t\t// This prevents command execution and shows help instead\n\t\tif helpRequested {\n\t\t\tif err := fs.Set(\"help\", \"true\"); err == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// If setting help flag fails, still return nil to avoid validation errors\n\t\t\treturn nil\n\t\t}\n\n\t\t// Validate the remaining plugin keys\n\t\tfor i, key := range validPluginKeys {\n\t\t\tif err := plugin.ValidateKey(key); err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid plugin %q found in flags: %w\", validPluginKeys[i], err)\n\t\t\t}\n\t\t}\n\n\t\tc.pluginKeys = validPluginKeys\n\t}\n\n\t// If the project version flag was accepted but not provided keep the empty version and try to resolve it later,\n\t// else validate the provided project version\n\tif projectVersionStr != \"\" {\n\t\tif err := c.projectVersion.Parse(projectVersionStr); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid project version flag: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// getInfoFromDefaults obtains the plugin keys, and maybe the project version from the default values\nfunc (c *CLI) getInfoFromDefaults() {\n\t// Should not use default values if a plugin was already set\n\t// This checks includes the case where a project configuration file was found,\n\t// as it will always have at least one plugin key set by now\n\tif len(c.pluginKeys) != 0 {\n\t\t// We don't assign a default value for project version here because we may be able to\n\t\t// resolve the project version after resolving the plugins.\n\t\treturn\n\t}\n\n\t// If the user provided a project version, use the default plugins for that project version\n\tif c.projectVersion.Validate() == nil {\n\t\tc.pluginKeys = c.defaultPlugins[c.projectVersion]\n\t\treturn\n\t}\n\n\t// Else try to use the default plugins for the default project version\n\tif c.defaultProjectVersion.Validate() == nil {\n\t\tvar found bool\n\t\tif c.pluginKeys, found = c.defaultPlugins[c.defaultProjectVersion]; found {\n\t\t\tc.projectVersion = c.defaultProjectVersion\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Else check if only default plugins for a project version were provided\n\tif len(c.defaultPlugins) == 1 {\n\t\tfor projectVersion, defaultPlugins := range c.defaultPlugins {\n\t\t\tc.pluginKeys = defaultPlugins\n\t\t\tc.projectVersion = projectVersion\n\t\t\treturn\n\t\t}\n\t}\n}\n\nconst unstablePluginMsg = \" (plugin version is unstable, there may be an upgrade available: \" +\n\t\"https://kubebuilder.io/plugins/plugins-versioning)\"\n\n// resolvePlugins selects from the available plugins those that match the project version and plugin keys provided.\nfunc (c *CLI) resolvePlugins() error {\n\tknownProjectVersion := c.projectVersion.Validate() == nil\n\n\tfor _, pluginKey := range c.pluginKeys {\n\t\tvar extraErrMsg string\n\n\t\tplugins := make([]plugin.Plugin, 0, len(c.plugins))\n\t\tfor _, p := range c.plugins {\n\t\t\tplugins = append(plugins, p)\n\t\t}\n\t\t// We can omit the error because plugin keys have already been validated\n\t\tplugins, _ = plugin.FilterPluginsByKey(plugins, pluginKey)\n\t\tif knownProjectVersion {\n\t\t\tplugins = plugin.FilterPluginsByProjectVersion(plugins, c.projectVersion)\n\t\t\textraErrMsg += fmt.Sprintf(\" for project version %q\", c.projectVersion)\n\t\t}\n\n\t\t// Plugins are often released as \"unstable\" (alpha/beta) versions, then upgraded to \"stable\".\n\t\t// This upgrade effectively removes a plugin, which is fine because unstable plugins are\n\t\t// under no support contract. However users should be notified _why_ their plugin cannot be found.\n\t\tif _, version := plugin.SplitKey(pluginKey); version != \"\" {\n\t\t\tvar ver plugin.Version\n\t\t\tif err := ver.Parse(version); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error parsing input plugin version from key %q: %w\", pluginKey, err)\n\t\t\t}\n\t\t\tif !ver.IsStable() {\n\t\t\t\textraErrMsg += unstablePluginMsg\n\t\t\t}\n\t\t}\n\n\t\t// Only 1 plugin can match\n\t\tswitch len(plugins) {\n\t\tcase 1:\n\t\t\tc.resolvedPlugins = append(c.resolvedPlugins, plugins[0])\n\t\tcase 0:\n\t\t\treturn fmt.Errorf(\"no plugin could be resolved with key %q%s\", pluginKey, extraErrMsg)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"ambiguous plugin %q%s\", pluginKey, extraErrMsg)\n\t\t}\n\t}\n\n\t// Now we can try to resolve the project version if not known by this point\n\tif !knownProjectVersion && len(c.resolvedPlugins) > 0 {\n\t\t// Extract the common supported project versions\n\t\tsupportedProjectVersions := plugin.CommonSupportedProjectVersions(c.resolvedPlugins...)\n\n\t\t// If there is only one common supported project version, resolve to it\n\tProjectNumberVersionSwitch:\n\t\tswitch len(supportedProjectVersions) {\n\t\tcase 1:\n\t\t\tc.projectVersion = supportedProjectVersions[0]\n\t\tcase 0:\n\t\t\treturn fmt.Errorf(\"no project version supported by all the resolved plugins\")\n\t\tdefault:\n\t\t\tsupportedProjectVersionStrings := make([]string, 0, len(supportedProjectVersions))\n\t\t\tfor _, supportedProjectVersion := range supportedProjectVersions {\n\t\t\t\t// In case one of the multiple supported versions is the default one, choose that and exit the switch\n\t\t\t\tif supportedProjectVersion.Compare(c.defaultProjectVersion) == 0 {\n\t\t\t\t\tc.projectVersion = c.defaultProjectVersion\n\t\t\t\t\tbreak ProjectNumberVersionSwitch\n\t\t\t\t}\n\t\t\t\tsupportedProjectVersionStrings = append(supportedProjectVersionStrings,\n\t\t\t\t\tfmt.Sprintf(\"%q\", supportedProjectVersion))\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"ambiguous project version, resolved plugins support the following project versions: %s\",\n\t\t\t\tstrings.Join(supportedProjectVersionStrings, \", \"))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// addSubcommands returns a root command with a subcommand tree reflecting the\n// current project's state.\nfunc (c *CLI) addSubcommands() {\n\t// add the alpha command if it has any subcommands enabled\n\tc.addAlphaCmd()\n\n\t// kubebuilder completion\n\t// Only add completion if requested\n\tif c.completionCommand {\n\t\tc.cmd.AddCommand(c.newCompletionCmd())\n\t}\n\n\t// kubebuilder create\n\tcreateCmd := c.newCreateCmd()\n\t// kubebuilder create api\n\tcreateCmd.AddCommand(c.newCreateAPICmd())\n\tcreateCmd.AddCommand(c.newCreateWebhookCmd())\n\tif createCmd.HasSubCommands() {\n\t\tc.cmd.AddCommand(createCmd)\n\t}\n\n\t// kubebuilder edit\n\tc.cmd.AddCommand(c.newEditCmd())\n\n\t// kubebuilder init\n\tc.cmd.AddCommand(c.newInitCmd())\n\n\t// kubebuilder version\n\t// Only add version if a version string was provided\n\tif c.version != \"\" {\n\t\tc.cmd.AddCommand(c.newVersionCmd())\n\t}\n}\n\n// addExtraCommands adds the additional commands.\nfunc (c *CLI) addExtraCommands() error {\n\tfor _, cmd := range c.extraCommands {\n\t\tfor _, subCmd := range c.cmd.Commands() {\n\t\t\tif cmd.Name() == subCmd.Name() {\n\t\t\t\treturn fmt.Errorf(\"command %q already exists\", cmd.Name())\n\t\t\t}\n\t\t}\n\t\tc.cmd.AddCommand(cmd)\n\t}\n\treturn nil\n}\n\n// printDeprecationWarnings prints the deprecation warnings of the resolved plugins.\nfunc (c CLI) printDeprecationWarnings() {\n\tfor _, p := range c.resolvedPlugins {\n\t\tif p != nil && p.(plugin.Deprecated) != nil && len(p.(plugin.Deprecated).DeprecationWarning()) > 0 {\n\t\t\t_, _ = fmt.Fprintf(os.Stderr, noticeColor, fmt.Sprintf(deprecationFmt, p.(plugin.Deprecated).DeprecationWarning()))\n\t\t}\n\t}\n}\n\n// metadata returns CLI's metadata.\nfunc (c CLI) metadata() plugin.CLIMetadata {\n\treturn plugin.CLIMetadata{\n\t\tCommandName: c.commandName,\n\t}\n}\n\n// Run executes the CLI utility.\n//\n// If an error is found, command help and examples will be printed.\nfunc (c CLI) Run() error {\n\tif err := c.cmd.Execute(); err != nil {\n\t\t// Don't return error if help was displayed (from --plugins --help pattern)\n\t\tif err == errHelpDisplayed {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"error executing command: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Command returns the underlying root command.\nfunc (c CLI) Command() *cobra.Command {\n\treturn c.cmd\n}\n"
  },
  {
    "path": "pkg/cli/cli_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\tgolangv4 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4\"\n)\n\nfunc makeMockPluginsFor(projectVersion config.Version, pluginKeys ...string) []plugin.Plugin {\n\tplugins := make([]plugin.Plugin, 0, len(pluginKeys))\n\tfor _, key := range pluginKeys {\n\t\tn, v := plugin.SplitKey(key)\n\t\tplugins = append(plugins, newMockPlugin(n, v, projectVersion))\n\t}\n\treturn plugins\n}\n\nfunc makeMapFor(plugins ...plugin.Plugin) map[string]plugin.Plugin {\n\tpluginMap := make(map[string]plugin.Plugin, len(plugins))\n\tfor _, p := range plugins {\n\t\tpluginMap[plugin.KeyFor(p)] = p\n\t}\n\treturn pluginMap\n}\n\nfunc setFlag(flag, value string) {\n\tos.Args = append(os.Args, \"subcommand\", \"--\"+flag, value)\n}\n\nfunc setBoolFlag(flag string) {\n\tos.Args = append(os.Args, \"subcommand\", \"--\"+flag)\n}\n\nfunc setProjectVersionFlag(value string) {\n\tsetFlag(projectVersionFlag, value)\n}\n\nfunc setPluginsFlag(value string) {\n\tsetFlag(pluginsFlag, value)\n}\n\nfunc hasSubCommand(cmd *cobra.Command, name string) bool {\n\tfor _, subcommand := range cmd.Commands() {\n\t\tif subcommand.Name() == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype pluginChainCapturingSubcommand struct {\n\tpluginChain []string\n}\n\nfunc (s *pluginChainCapturingSubcommand) Scaffold(machinery.Filesystem) error {\n\treturn nil\n}\n\nfunc (s *pluginChainCapturingSubcommand) SetPluginChain(chain []string) {\n\ts.pluginChain = append([]string(nil), chain...)\n}\n\ntype testCreateAPIPlugin struct {\n\tname        string\n\tversion     plugin.Version\n\tsubcommand  *testCreateAPISubcommand\n\tprojectVers []config.Version\n}\n\nfunc newTestCreateAPIPlugin(name string, version plugin.Version) testCreateAPIPlugin {\n\treturn testCreateAPIPlugin{\n\t\tname:        name,\n\t\tversion:     version,\n\t\tsubcommand:  &testCreateAPISubcommand{},\n\t\tprojectVers: []config.Version{{Number: 3}},\n\t}\n}\n\nfunc (p testCreateAPIPlugin) Name() string                               { return p.name }\nfunc (p testCreateAPIPlugin) Version() plugin.Version                    { return p.version }\nfunc (p testCreateAPIPlugin) SupportedProjectVersions() []config.Version { return p.projectVers }\nfunc (p testCreateAPIPlugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand {\n\treturn p.subcommand\n}\n\ntype testCreateAPISubcommand struct{}\n\nfunc (s *testCreateAPISubcommand) InjectResource(*resource.Resource) error {\n\treturn nil\n}\n\nfunc (s *testCreateAPISubcommand) Scaffold(machinery.Filesystem) error {\n\treturn nil\n}\n\ntype fakeStore struct {\n\tcfg config.Config\n}\n\nfunc (f *fakeStore) New(config.Version) error { return nil }\nfunc (f *fakeStore) Load() error              { return nil }\nfunc (f *fakeStore) LoadFrom(string) error    { return nil }\nfunc (f *fakeStore) Save() error              { return nil }\nfunc (f *fakeStore) SaveTo(string) error      { return nil }\nfunc (f *fakeStore) Config() config.Config    { return f.cfg }\n\ntype captureSubcommand struct {\n\tlastChain []string\n}\n\nfunc (c *captureSubcommand) Scaffold(machinery.Filesystem) error { return nil }\n\nvar _ = Describe(\"CLI\", func() {\n\tvar (\n\t\tc              *CLI\n\t\tprojectVersion config.Version\n\t)\n\n\tBeforeEach(func() {\n\t\tc = &CLI{\n\t\t\tfs: machinery.Filesystem{FS: afero.NewMemMapFs()},\n\t\t}\n\n\t\tprojectVersion = config.Version{Number: 3}\n\t})\n\n\tDescribe(\"filterSubcommands\", func() {\n\t\tIt(\"propagates bundle keys to wrapped subcommands\", func() {\n\t\t\tbundleVersion := plugin.Version{Number: 1, Stage: stage.Alpha}\n\n\t\t\tfooPlugin := newTestCreateAPIPlugin(\"deploy-image.go.kubebuilder.io\", plugin.Version{Number: 1, Stage: stage.Alpha})\n\t\t\tbarPlugin := newTestCreateAPIPlugin(\"deploy-image.go.kubebuilder.io\", plugin.Version{Number: 1, Stage: stage.Alpha})\n\n\t\t\tfooBundle, err := plugin.NewBundleWithOptions(\n\t\t\t\tplugin.WithName(\"deploy-image.foo.example.com\"),\n\t\t\t\tplugin.WithVersion(bundleVersion),\n\t\t\t\tplugin.WithPlugins(fooPlugin),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tbarBundle, err := plugin.NewBundleWithOptions(\n\t\t\t\tplugin.WithName(\"deploy-image.bar.example.com\"),\n\t\t\t\tplugin.WithVersion(bundleVersion),\n\t\t\t\tplugin.WithPlugins(barPlugin),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tc.resolvedPlugins = []plugin.Plugin{fooBundle, barBundle}\n\n\t\t\ttuples := c.filterSubcommands(\n\t\t\t\tfunc(p plugin.Plugin) bool {\n\t\t\t\t\t_, isCreateAPI := p.(plugin.CreateAPI)\n\t\t\t\t\treturn isCreateAPI\n\t\t\t\t},\n\t\t\t\tfunc(p plugin.Plugin) plugin.Subcommand {\n\t\t\t\t\treturn p.(plugin.CreateAPI).GetCreateAPISubcommand()\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tExpect(tuples).To(HaveLen(2))\n\t\t\tExpect(tuples[0].key).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t\t\tExpect(tuples[0].configKey).To(Equal(\"deploy-image.foo.example.com/v1-alpha\"))\n\t\t\tExpect(tuples[1].key).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t\t\tExpect(tuples[1].configKey).To(Equal(\"deploy-image.bar.example.com/v1-alpha\"))\n\t\t})\n\t})\n\n\tDescribe(\"executionHooksFactory\", func() {\n\t\tIt(\"temporarily reorders the plugin chain while invoking bundled subcommands\", func() {\n\t\t\tcfg := cfgv3.New()\n\t\t\tExpect(cfg.SetPluginChain([]string{\n\t\t\t\t\"deploy-image.foo.example.com/v1-alpha\",\n\t\t\t\t\"deploy-image.bar.example.com/v1-alpha\",\n\t\t\t})).To(Succeed())\n\n\t\t\tstore := &fakeStore{cfg: cfg}\n\t\t\tfirst := &captureSubcommand{}\n\t\t\tsecond := &captureSubcommand{}\n\n\t\t\tfactory := executionHooksFactory{\n\t\t\t\tstore: store,\n\t\t\t\tsubcommands: []keySubcommandTuple{\n\t\t\t\t\t{configKey: \"deploy-image.foo.example.com/v1-alpha\", subcommand: first},\n\t\t\t\t\t{configKey: \"deploy-image.bar.example.com/v1-alpha\", subcommand: second},\n\t\t\t\t},\n\t\t\t\terrorMessage: \"test\",\n\t\t\t}\n\n\t\t\tcallErr := factory.forEach(func(sub plugin.Subcommand) error {\n\t\t\t\tcs := sub.(*captureSubcommand)\n\t\t\t\tcs.lastChain = append([]string(nil), store.Config().GetPluginChain()...)\n\t\t\t\treturn nil\n\t\t\t}, \"scaffold\")\n\t\t\tExpect(callErr).NotTo(HaveOccurred())\n\t\t\tExpect(first.lastChain[0]).To(Equal(\"deploy-image.foo.example.com/v1-alpha\"))\n\t\t\tExpect(second.lastChain[0]).To(Equal(\"deploy-image.bar.example.com/v1-alpha\"))\n\t\t\tExpect(store.Config().GetPluginChain()).To(Equal([]string{\n\t\t\t\t\"deploy-image.foo.example.com/v1-alpha\",\n\t\t\t\t\"deploy-image.bar.example.com/v1-alpha\",\n\t\t\t}))\n\t\t})\n\t})\n\n\tContext(\"buildCmd\", func() {\n\t\tvar projectFile string\n\n\t\tBeforeEach(func() {\n\t\t\tprojectFile = `domain: zeusville.com\nlayout: go.kubebuilder.io/v3\nprojectName: demo-zeus-operator\nrepo: github.com/jmrodri/demo-zeus-operator\nresources:\n- crdVersion: v1\n  group: test\n  kind: Test\n  version: v1\nversion: 3-alpha\nplugins:\n  manifests.sdk.operatorframework.io/v2: {}\n`\n\t\t\tf, err := c.fs.FS.Create(\"PROJECT\")\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t_, err = f.WriteString(projectFile)\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\t\t})\n\n\t\tWhen(\"reading a 3-alpha config\", func() {\n\t\t\tIt(\"should succeed and set the projectVersion\", func() {\n\t\t\t\terr := c.buildCmd()\n\t\t\t\tExpect(err).To(Not(HaveOccurred()))\n\t\t\t\tExpect(c.projectVersion.Compare(\n\t\t\t\t\tconfig.Version{\n\t\t\t\t\t\tNumber: 3,\n\t\t\t\t\t\tStage:  stage.Stable,\n\t\t\t\t\t})).To(Equal(0))\n\t\t\t})\n\t\t\tIt(\"should fail when stable is not registered \", func() {\n\t\t\t\t// overwrite project file with fake 4-alpha\n\t\t\t\tf, err := c.fs.FS.OpenFile(\"PROJECT\", os.O_WRONLY, 0)\n\t\t\t\tExpect(err).To(Not(HaveOccurred()))\n\t\t\t\t_, err = f.WriteString(strings.ReplaceAll(projectFile, \"3-alpha\", \"4-alpha\"))\n\t\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t\t// buildCmd should return an error\n\t\t\t\terr = c.buildCmd()\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\t})\n\n\t// TODO: test CLI.getInfoFromConfigFile using a mock filesystem\n\n\tContext(\"getInfoFromConfig\", func() {\n\t\tWhen(\"having a single plugin in the layout field\", func() {\n\t\t\tIt(\"should succeed\", func() {\n\t\t\t\tpluginChain := []string{\"go.kubebuilder.io/v4\"}\n\t\t\t\tprojectConfig := cfgv3.New()\n\t\t\t\tExpect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())\n\n\t\t\t\tExpect(c.getInfoFromConfig(projectConfig)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginChain))\n\t\t\t\tExpect(c.projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"having multiple plugins in the layout field\", func() {\n\t\t\tIt(\"should succeed\", func() {\n\t\t\t\tpluginChain := []string{\"go.kubebuilder.io/v2\", \"deploy-image.go.kubebuilder.io/v1-alpha\"}\n\n\t\t\t\tprojectConfig := cfgv3.New()\n\t\t\t\tExpect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())\n\n\t\t\t\tExpect(c.getInfoFromConfig(projectConfig)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginChain))\n\t\t\t\tExpect(c.projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"having invalid plugin keys in the layout field\", func() {\n\t\t\tIt(\"should fail\", func() {\n\t\t\t\tpluginChain := []string{\"_/v1\"}\n\n\t\t\t\tprojectConfig := cfgv3.New()\n\t\t\t\tExpect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())\n\n\t\t\t\tExpect(c.getInfoFromConfig(projectConfig)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"getInfoFromFlags\", func() {\n\t\t// Save os.Args and restore it for every test\n\t\tvar args []string\n\t\tBeforeEach(func() {\n\t\t\tc.cmd = c.newRootCmd()\n\n\t\t\targs = os.Args\n\t\t})\n\t\tAfterEach(func() {\n\t\t\tos.Args = args\n\t\t})\n\n\t\tWhen(\"no flag is set\", func() {\n\t\t\tIt(\"should succeed\", func() {\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(BeEmpty())\n\t\t\t\tExpect(c.projectVersion.Compare(config.Version{})).To(Equal(0))\n\t\t\t})\n\t\t})\n\n\t\tWhen(fmt.Sprintf(\"--%s flag is set\", pluginsFlag), func() {\n\t\t\tIt(\"should succeed using one plugin key\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \",\"))\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(config.Version{})).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should succeed using more than one plugin key\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\", \"example/v2\", \"test/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \",\"))\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(config.Version{})).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should succeed using more than one plugin key with spaces\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\", \"example/v2\", \"test/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \", \"))\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(config.Version{})).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should fail for an invalid plugin key\", func() {\n\t\t\t\tsetPluginsFlag(\"_/v1\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tWhen(fmt.Sprintf(\"--%s flag is set\", projectVersionFlag), func() {\n\t\t\tIt(\"should succeed\", func() {\n\t\t\t\tsetProjectVersionFlag(projectVersion.String())\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(BeEmpty())\n\t\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should fail for an invalid project version\", func() {\n\t\t\t\tsetProjectVersionFlag(\"v_1\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tWhen(fmt.Sprintf(\"--%s and --%s flags are set\", pluginsFlag, projectVersionFlag), func() {\n\t\t\tIt(\"should succeed using one plugin key\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \",\"))\n\t\t\t\tsetProjectVersionFlag(projectVersion.String())\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should succeed using more than one plugin key\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\", \"example/v2\", \"test/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \",\"))\n\t\t\t\tsetProjectVersionFlag(projectVersion.String())\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t\t})\n\n\t\t\tIt(\"should succeed using more than one plugin key with spaces\", func() {\n\t\t\t\tpluginKeys := []string{\"go/v1\", \"example/v2\", \"test/v1\"}\n\t\t\t\tsetPluginsFlag(strings.Join(pluginKeys, \", \"))\n\t\t\t\tsetProjectVersionFlag(projectVersion.String())\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"additional flags are set\", func() {\n\t\t\tIt(\"should succeed\", func() {\n\t\t\t\tsetFlag(\"extra-flag\", \"extra-value\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t})\n\n\t\t\t// `--help` is not captured by the allowlist, so we need to special case it\n\t\t\tIt(\"should not fail for `--help`\", func() {\n\t\t\t\tsetBoolFlag(\"help\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t})\n\n\t\t\t// When --plugins is followed by --help, --help is consumed as plugin value\n\t\t\t// This should not trigger plugin validation errors\n\t\t\tIt(\"should not fail when `--plugins --help` is used together\", func() {\n\t\t\t\tos.Args = append(os.Args, \"edit\", \"--plugins\", \"--help\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(BeEmpty())\n\t\t\t})\n\n\t\t\t// Same test for short help flag\n\t\t\tIt(\"should not fail when `--plugins -h` is used together\", func() {\n\t\t\t\tos.Args = append(os.Args, \"edit\", \"--plugins\", \"-h\")\n\n\t\t\t\tExpect(c.getInfoFromFlags(false)).To(Succeed())\n\t\t\t\tExpect(c.pluginKeys).To(BeEmpty())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"getInfoFromDefaults\", func() {\n\t\tvar pluginKeys []string\n\n\t\tBeforeEach(func() {\n\t\t\tpluginKeys = []string{\"go.kubebuilder.io/v2\"}\n\t\t})\n\n\t\tIt(\"should be a no-op if already have plugin keys\", func() {\n\t\t\tc.pluginKeys = pluginKeys\n\n\t\t\tc.getInfoFromDefaults()\n\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\tExpect(c.projectVersion.Compare(config.Version{})).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should succeed if default plugins for project version are set\", func() {\n\t\t\tc.projectVersion = projectVersion\n\t\t\tc.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}\n\n\t\t\tc.getInfoFromDefaults()\n\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should succeed if default plugins for default project version are set\", func() {\n\t\t\tc.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}\n\t\t\tc.defaultProjectVersion = projectVersion\n\n\t\t\tc.getInfoFromDefaults()\n\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should succeed if default plugins for only a single project version are set\", func() {\n\t\t\tc.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}\n\n\t\t\tc.getInfoFromDefaults()\n\t\t\tExpect(c.pluginKeys).To(Equal(pluginKeys))\n\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t})\n\t})\n\n\tContext(\"resolvePlugins\", func() {\n\t\tBeforeEach(func() {\n\t\t\tpluginKeys := []string{\n\t\t\t\t\"foo.example.com/v1\",\n\t\t\t\t\"bar.example.com/v1\",\n\t\t\t\t\"baz.example.com/v1\",\n\t\t\t\t\"foo.kubebuilder.io/v1\",\n\t\t\t\t\"foo.kubebuilder.io/v2\",\n\t\t\t\t\"bar.kubebuilder.io/v1\",\n\t\t\t\t\"bar.kubebuilder.io/v2\",\n\t\t\t}\n\n\t\t\tplugins := makeMockPluginsFor(projectVersion, pluginKeys...)\n\t\t\tplugins = append(plugins,\n\t\t\t\tnewMockPlugin(\"invalid.kubebuilder.io\", \"v1\"),\n\t\t\t\tnewMockPlugin(\"only1.kubebuilder.io\", \"v1\",\n\t\t\t\t\tconfig.Version{Number: 1}),\n\t\t\t\tnewMockPlugin(\"only2.kubebuilder.io\", \"v1\",\n\t\t\t\t\tconfig.Version{Number: 2}),\n\t\t\t\tnewMockPlugin(\"1and2.kubebuilder.io\", \"v1\",\n\t\t\t\t\tconfig.Version{Number: 1}, config.Version{Number: 2}),\n\t\t\t\tnewMockPlugin(\"2and3.kubebuilder.io\", \"v1\",\n\t\t\t\t\tconfig.Version{Number: 2}, config.Version{Number: 3}),\n\t\t\t\tnewMockPlugin(\"1-2and3.kubebuilder.io\", \"v1\",\n\t\t\t\t\tconfig.Version{Number: 1}, config.Version{Number: 2}, config.Version{Number: 3}),\n\t\t\t)\n\t\t\tpluginMap := makeMapFor(plugins...)\n\n\t\t\tc.plugins = pluginMap\n\t\t})\n\n\t\tDescribeTable(\"should resolve\",\n\t\t\tfunc(key, qualified string) {\n\t\t\t\tc.pluginKeys = []string{key}\n\t\t\t\tc.projectVersion = projectVersion\n\n\t\t\t\tExpect(c.resolvePlugins()).To(Succeed())\n\t\t\t\tExpect(c.resolvedPlugins).To(HaveLen(1))\n\t\t\t\tExpect(plugin.KeyFor(c.resolvedPlugins[0])).To(Equal(qualified))\n\t\t\t},\n\t\t\tEntry(\"fully qualified plugin\", \"foo.example.com/v1\", \"foo.example.com/v1\"),\n\t\t\tEntry(\"plugin without version\", \"foo.example.com\", \"foo.example.com/v1\"),\n\t\t\tEntry(\"shortname without version\", \"baz\", \"baz.example.com/v1\"),\n\t\t\tEntry(\"shortname with version\", \"foo/v2\", \"foo.kubebuilder.io/v2\"),\n\t\t)\n\n\t\tDescribeTable(\"should not resolve\",\n\t\t\tfunc(key string) {\n\t\t\t\tc.pluginKeys = []string{key}\n\t\t\t\tc.projectVersion = projectVersion\n\n\t\t\t\tExpect(c.resolvePlugins()).NotTo(Succeed())\n\t\t\t},\n\t\t\tEntry(\"for an ambiguous version\", \"foo.kubebuilder.io\"),\n\t\t\tEntry(\"for an ambiguous name\", \"foo/v1\"),\n\t\t\tEntry(\"for an ambiguous name and version\", \"foo\"),\n\t\t\tEntry(\"for a non-existent name\", \"blah\"),\n\t\t\tEntry(\"for a non-existent version\", \"foo.example.com/v2\"),\n\t\t\tEntry(\"for a non-existent version\", \"foo/v3\"),\n\t\t\tEntry(\"for a non-existent version\", \"foo.example.com/v3\"),\n\t\t\tEntry(\"for a plugin that doesn't support the project version\", \"invalid.kubebuilder.io/v1\"),\n\t\t)\n\n\t\tIt(\"should succeed if only one common project version is found\", func() {\n\t\t\tc.pluginKeys = []string{\"1and2\", \"2and3\"}\n\n\t\t\tExpect(c.resolvePlugins()).To(Succeed())\n\t\t\tExpect(c.projectVersion.Compare(config.Version{Number: 2})).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should fail if no common project version is found\", func() {\n\t\t\tc.pluginKeys = []string{\"only1\", \"only2\"}\n\n\t\t\tExpect(c.resolvePlugins()).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should fail if more than one common project versions are found\", func() {\n\t\t\tc.pluginKeys = []string{\"1and2\", \"1-2and3\"}\n\n\t\t\tExpect(c.resolvePlugins()).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed if more than one common project versions are found and one is the default\", func() {\n\t\t\tc.pluginKeys = []string{\"2and3\", \"1-2and3\"}\n\t\t\tc.defaultProjectVersion = projectVersion\n\n\t\t\tExpect(c.resolvePlugins()).To(Succeed())\n\t\t\tExpect(c.projectVersion.Compare(projectVersion)).To(Equal(0))\n\t\t})\n\t})\n\n\tContext(\"applySubcommandHooks\", func() {\n\t\tvar (\n\t\t\tcmd        *cobra.Command\n\t\t\tsub1, sub2 *pluginChainCapturingSubcommand\n\t\t\ttuples     []keySubcommandTuple\n\t\t\tchainKeys  []string\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tcmd = &cobra.Command{}\n\t\t\tsub1 = &pluginChainCapturingSubcommand{}\n\t\t\tsub2 = &pluginChainCapturingSubcommand{}\n\t\t\ttuples = []keySubcommandTuple{\n\t\t\t\t{key: \"alpha.kubebuilder.io/v1\", subcommand: sub1},\n\t\t\t\t{key: \"beta.kubebuilder.io/v1\", subcommand: sub2},\n\t\t\t}\n\t\t\tchainKeys = []string{\"alpha.kubebuilder.io/v1\", \"beta.kubebuilder.io/v1\"}\n\t\t})\n\n\t\tIt(\"sets the plugin chain on subcommands\", func() {\n\t\t\tc.applySubcommandHooks(cmd, tuples, \"test\", false)\n\n\t\t\tExpect(sub1.pluginChain).To(Equal(chainKeys))\n\t\t\tExpect(sub2.pluginChain).To(Equal(chainKeys))\n\t\t})\n\n\t\tIt(\"sets the plugin chain when creating a new configuration\", func() {\n\t\t\tc.resolvedPlugins = makeMockPluginsFor(projectVersion, chainKeys...)\n\n\t\t\tc.applySubcommandHooks(cmd, tuples, \"test\", true)\n\n\t\t\tExpect(sub1.pluginChain).To(Equal(chainKeys))\n\t\t\tExpect(sub2.pluginChain).To(Equal(chainKeys))\n\t\t})\n\t})\n\n\tContext(\"New\", func() {\n\t\tvar c *CLI\n\t\tvar err error\n\n\t\tWhen(\"no option is provided\", func() {\n\t\t\tIt(\"should create a valid CLI\", func() {\n\t\t\t\t_, err = New()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\t// NOTE: Options are extensively tested in their own tests.\n\t\t//       The ones tested here ensure better coverage.\n\n\t\tWhen(\"providing a version string\", func() {\n\t\t\tIt(\"should create a valid CLI\", func() {\n\t\t\t\tconst version = \"version string\"\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithVersion(version),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(hasSubCommand(c.cmd, \"version\")).To(BeTrue())\n\n\t\t\t\t// Test the version command\n\t\t\t\tc.cmd.SetArgs([]string{\"version\"})\n\t\t\t\t// Overwrite stdout to read the output and reset it afterwards\n\t\t\t\tr, w, _ := os.Pipe()\n\t\t\t\ttemp := os.Stdout\n\t\t\t\tdefer func() {\n\t\t\t\t\tos.Stdout = temp\n\t\t\t\t}()\n\t\t\t\tos.Stdout = w\n\t\t\t\tExpect(c.cmd.Execute()).Should(Succeed())\n\n\t\t\t\t_ = w.Close()\n\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tprinted, _ := io.ReadAll(r)\n\t\t\t\tExpect(string(printed)).To(Equal(\n\t\t\t\t\tfmt.Sprintf(\"%s\\n\", version)))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"enabling completion\", func() {\n\t\t\tIt(\"should create a valid CLI\", func() {\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithCompletion(),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(hasSubCommand(c.cmd, \"completion\")).To(BeTrue())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing an invalid option\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t// An empty project version is not valid\n\t\t\t\t_, err = New(WithDefaultProjectVersion(config.Version{}))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"being unable to resolve plugins\", func() {\n\t\t\t// Save os.Args and restore it for every test\n\t\t\tvar args []string\n\t\t\tBeforeEach(func() { args = os.Args })\n\t\t\tAfterEach(func() { os.Args = args })\n\n\t\t\tIt(\"should return a CLI that returns an error\", func() {\n\t\t\t\tsetPluginsFlag(\"foo\")\n\n\t\t\t\tc, err = New()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t// Overwrite stderr to read the output and reset it afterwards\n\t\t\t\t_, w, _ := os.Pipe()\n\t\t\t\ttemp := os.Stderr\n\t\t\t\tdefer func() {\n\t\t\t\t\tos.Stderr = temp\n\t\t\t\t\t_ = w.Close()\n\t\t\t\t}()\n\t\t\t\tos.Stderr = w\n\n\t\t\t\tExpect(c.Run()).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing extra commands\", func() {\n\t\t\tIt(\"should create a valid CLI for non-conflicting ones\", func() {\n\t\t\t\textraCommand := &cobra.Command{Use: \"extra\"}\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithExtraCommands(extraCommand),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(hasSubCommand(c.cmd, extraCommand.Use)).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should return an error for conflicting ones\", func() {\n\t\t\t\textraCommand := &cobra.Command{Use: \"init\"}\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithExtraCommands(extraCommand),\n\t\t\t\t)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing extra alpha commands\", func() {\n\t\t\tIt(\"should create a valid CLI for non-conflicting ones\", func() {\n\t\t\t\textraAlphaCommand := &cobra.Command{Use: \"extra\"}\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithExtraAlphaCommands(extraAlphaCommand),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tvar alpha *cobra.Command\n\t\t\t\tfor _, subcmd := range c.cmd.Commands() {\n\t\t\t\t\tif subcmd.Name() == alphaCommand {\n\t\t\t\t\t\talpha = subcmd\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tExpect(alpha).NotTo(BeNil())\n\t\t\t\tExpect(hasSubCommand(alpha, extraAlphaCommand.Use)).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should return an error for conflicting ones\", func() {\n\t\t\t\textraAlphaCommand := &cobra.Command{Use: \"extra\"}\n\t\t\t\t_, err = New(\n\t\t\t\t\tWithPlugins(&golangv4.Plugin{}),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, &golangv4.Plugin{}),\n\t\t\t\t\tWithExtraAlphaCommands(extraAlphaCommand, extraAlphaCommand),\n\t\t\t\t)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing deprecated plugins\", func() {\n\t\t\tIt(\"should succeed and print the deprecation notice\", func() {\n\t\t\t\tconst (\n\t\t\t\t\tdeprecationWarning = \"DEPRECATED\"\n\t\t\t\t)\n\t\t\t\tdeprecatedPlugin := newMockDeprecatedPlugin(\"deprecated\", \"v1\", deprecationWarning, projectVersion)\n\n\t\t\t\t// Overwrite stderr to read the deprecation output and reset it afterwards\n\t\t\t\tr, w, _ := os.Pipe()\n\t\t\t\ttemp := os.Stderr\n\t\t\t\tdefer func() {\n\t\t\t\t\tos.Stderr = temp\n\t\t\t\t}()\n\t\t\t\tos.Stderr = w\n\n\t\t\t\tc, err = New(\n\t\t\t\t\tWithPlugins(deprecatedPlugin),\n\t\t\t\t\tWithDefaultPlugins(projectVersion, deprecatedPlugin),\n\t\t\t\t\tWithDefaultProjectVersion(projectVersion),\n\t\t\t\t)\n\n\t\t\t\t_ = w.Close()\n\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tprinted, _ := io.ReadAll(r)\n\t\t\t\tExpect(string(printed)).To(Equal(\n\t\t\t\t\tfmt.Sprintf(noticeColor, fmt.Sprintf(deprecationFmt, deprecationWarning))))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"new succeeds\", func() {\n\t\t\tIt(\"should return the underlying command\", func() {\n\t\t\t\tc, err = New()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(c.Command()).NotTo(BeNil())\n\t\t\t\tExpect(c.Command()).To(Equal(c.cmd))\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/cmd_helpers.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\tyamlstore \"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\n// noResolvedPluginError is returned by subcommands that require a plugin when none was resolved.\ntype noResolvedPluginError struct{}\n\n// Error implements error interface.\nfunc (e noResolvedPluginError) Error() string {\n\treturn \"no resolved plugin, please verify the project version and plugins specified in flags or configuration file\"\n}\n\n// noAvailablePluginError is returned by subcommands that require a plugin when none of their specific type was found.\ntype noAvailablePluginError struct {\n\tsubcommand string\n}\n\n// Error implements error interface.\nfunc (e noAvailablePluginError) Error() string {\n\treturn fmt.Sprintf(\"resolved plugins do not provide any %s subcommand\", e.subcommand)\n}\n\n// cmdErr updates a cobra command to output error information when executed\n// or used with the help flag.\nfunc cmdErr(cmd *cobra.Command, err error) {\n\tcmd.Long = fmt.Sprintf(\"%s\\nNote: %v\", cmd.Long, err)\n\tcmd.RunE = errCmdFunc(err)\n}\n\n// errCmdFunc returns a cobra RunE function that returns the provided error\nfunc errCmdFunc(err error) func(*cobra.Command, []string) error {\n\treturn func(*cobra.Command, []string) error {\n\t\treturn err\n\t}\n}\n\n// keySubcommandTuple pairs a plugin key with its subcommand.\n// key is the plugin's own key, configKey is the bundle key (if wrapped in a bundle).\ntype keySubcommandTuple struct {\n\tkey        string\n\tconfigKey  string\n\tsubcommand plugin.Subcommand\n\n\t// skip marks subcommands that should be skipped after a plugin.ExitError.\n\tskip bool\n}\n\ntype pluginChainSetter interface {\n\tSetPluginChain([]string)\n}\n\n// filterSubcommands returns plugin keys and subcommands from resolved plugins.\nfunc (c *CLI) filterSubcommands(\n\tfilter func(plugin.Plugin) bool,\n\textract func(plugin.Plugin) plugin.Subcommand,\n) []keySubcommandTuple {\n\ttuples := make([]keySubcommandTuple, 0, len(c.resolvedPlugins))\n\tfor _, p := range c.resolvedPlugins {\n\t\ttuples = append(tuples, collectSubcommands(p, plugin.KeyFor(p), filter, extract)...)\n\t}\n\treturn tuples\n}\n\nfunc collectSubcommands(\n\tp plugin.Plugin,\n\tconfigKey string,\n\tfilter func(plugin.Plugin) bool,\n\textract func(plugin.Plugin) plugin.Subcommand,\n) []keySubcommandTuple {\n\tif bundle, isBundle := p.(plugin.Bundle); isBundle {\n\t\tcollected := make([]keySubcommandTuple, 0, len(bundle.Plugins()))\n\t\tfor _, nested := range bundle.Plugins() {\n\t\t\tcollected = append(collected, collectSubcommands(nested, configKey, filter, extract)...)\n\t\t}\n\t\treturn collected\n\t}\n\n\tif !filter(p) {\n\t\treturn nil\n\t}\n\n\treturn []keySubcommandTuple{{\n\t\tkey:        plugin.KeyFor(p),\n\t\tconfigKey:  configKey,\n\t\tsubcommand: extract(p),\n\t}}\n}\n\n// applySubcommandHooks runs the initialization hooks and wires pre-run, run, and post-run for the command.\n// Used by init, create api, create webhook, and edit. When several plugins define the same flag,\n// one flag is shown and its value is synced to all plugins after parse.\nfunc (c *CLI) applySubcommandHooks(\n\tcmd *cobra.Command,\n\tsubcommands []keySubcommandTuple,\n\terrorMessage string,\n\tcreateConfig bool,\n) {\n\tcommandPluginChain := make([]string, len(subcommands))\n\tfor i, tuple := range subcommands {\n\t\tcommandPluginChain[i] = tuple.key\n\t}\n\tfor _, tuple := range subcommands {\n\t\tif setter, ok := tuple.subcommand.(pluginChainSetter); ok {\n\t\t\tsetter.SetPluginChain(commandPluginChain)\n\t\t}\n\t}\n\n\t// In case we create a new project configuration we need to compute the plugin chain.\n\tpluginChain := make([]string, 0, len(c.resolvedPlugins))\n\tif createConfig {\n\t\t// We extract the plugin keys again instead of using the ones obtained when filtering subcommands\n\t\t// as these plugins are unbundled but we want to keep bundle names in the plugin chain.\n\t\tfor _, p := range c.resolvedPlugins {\n\t\t\tpluginChain = append(pluginChain, plugin.KeyFor(p))\n\t\t}\n\t}\n\n\tresult, err := initializationHooks(cmd, subcommands, c.metadata())\n\tif err != nil {\n\t\tcmdErr(cmd, err)\n\t\treturn\n\t}\n\n\tfactory := executionHooksFactory{\n\t\tfs:                  c.fs,\n\t\tstore:               yamlstore.New(c.fs),\n\t\tsubcommands:         subcommands,\n\t\terrorMessage:        errorMessage,\n\t\tprojectVersion:      c.projectVersion,\n\t\tpluginChain:         pluginChain,\n\t\tcliVersion:          c.cliVersion,\n\t\tduplicateFlagValues: result.duplicateFlagValues,\n\t}\n\tcmd.PreRunE = factory.preRunEFunc(result.options, createConfig)\n\tcmd.RunE = factory.runEFunc()\n\tcmd.PostRunE = factory.postRunEFunc()\n}\n\n// appendPluginTable appends a filtered plugin table to the command's Long description.\n// For subcommands, it excludes the default scaffold and its component plugins.\nfunc (c *CLI) appendPluginTable(cmd *cobra.Command, filter func(plugin.Plugin) bool, title string) {\n\tpluginTable := c.getPluginTableFilteredForSubcommand(filter)\n\tcmd.Long = fmt.Sprintf(\"%s\\n%s:\\n\\n%s\\n\", cmd.Long, title, pluginTable)\n}\n\n// initHooksResult holds the result of initializationHooks: resource options and\n// duplicate-flag values to sync after parse.\ntype initHooksResult struct {\n\toptions             *resourceOptions\n\tduplicateFlagValues map[string][]pflag.Value\n}\n\n// mergeFlagSetInto merges flags from src into dest using AddFlagSet. If a flag name already exists,\n// the flag is not added again; its Value is stored in duplicateValues for later sync and the existing\n// Usage is extended. Returns an error if the same flag name is used with a different value type.\nfunc mergeFlagSetInto(\n\tdest *pflag.FlagSet,\n\tsrc *pflag.FlagSet,\n\tduplicateValues map[string][]pflag.Value,\n\tpluginKey string,\n\tfirstPluginByFlag map[string]string,\n) error {\n\tdestNames := make(map[string]struct{})\n\tdest.VisitAll(func(f *pflag.Flag) {\n\t\tdestNames[f.Name] = struct{}{}\n\t})\n\tdest.AddFlagSet(src)\n\n\tvar err error\n\tsrc.VisitAll(func(flag *pflag.Flag) {\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\texisting := dest.Lookup(flag.Name)\n\t\tif _, wasInDest := destNames[flag.Name]; !wasInDest {\n\t\t\tfirstPluginByFlag[flag.Name] = pluginKey\n\t\t\texisting.Usage = \"For plugin (\" + pluginKey + \"): \" + strings.TrimSpace(flag.Usage)\n\t\t\treturn\n\t\t}\n\t\tif existing.Value.Type() != flag.Value.Type() {\n\t\t\tfirstKey := firstPluginByFlag[flag.Name]\n\t\t\terr = fmt.Errorf(\n\t\t\t\t\"plugins %q and %q use the same flag name %q but expect different value types: one %s, other %s\",\n\t\t\t\tfirstKey, pluginKey, flag.Name, existing.Value.Type(), flag.Value.Type(),\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tduplicateValues[flag.Name] = append(duplicateValues[flag.Name], flag.Value)\n\t\texisting.Usage += \" AND for plugin (\" + pluginKey + \"): \" + strings.TrimSpace(flag.Usage)\n\t})\n\treturn err\n}\n\n// syncDuplicateFlags copies the parsed value of each flag to all duplicate Values from merge.\n// Call after the command has parsed flags (e.g. at the start of PreRunE).\nfunc syncDuplicateFlags(flags *pflag.FlagSet, duplicateValues map[string][]pflag.Value) {\n\tfor name, values := range duplicateValues {\n\t\tparsed := flags.Lookup(name)\n\t\tif parsed == nil {\n\t\t\tcontinue\n\t\t}\n\t\tsrcVal := parsed.Value.String()\n\t\tfor _, v := range values {\n\t\t\t_ = v.Set(srcVal)\n\t\t}\n\t}\n}\n\n// initializationHooks runs update-metadata and bind-flags hooks. When multiple plugins bind the same\n// flag, one flag is used and its value is synced to all after parse; usage text is aggregated.\n// Returns an error if the same flag name is used with different value types (e.g. bool vs string).\nfunc initializationHooks(\n\tcmd *cobra.Command,\n\tsubcommands []keySubcommandTuple,\n\tmeta plugin.CLIMetadata,\n) (*initHooksResult, error) {\n\t// Update metadata hook.\n\tsubcmdMeta := plugin.SubcommandMetadata{\n\t\tDescription: cmd.Long,\n\t\tExamples:    cmd.Example,\n\t}\n\tfor _, tuple := range subcommands {\n\t\tif subcommand, updatesMetadata := tuple.subcommand.(plugin.UpdatesMetadata); updatesMetadata {\n\t\t\tsubcommand.UpdateMetadata(meta, &subcmdMeta)\n\t\t}\n\t}\n\tcmd.Long = subcmdMeta.Description\n\tcmd.Example = subcmdMeta.Examples\n\n\t// Before binding specific plugin flags, bind common ones.\n\trequiresResource := false\n\tfor _, tuple := range subcommands {\n\t\tif _, requiresResource = tuple.subcommand.(plugin.RequiresResource); requiresResource {\n\t\t\tbreak\n\t\t}\n\t}\n\tvar options *resourceOptions\n\tif requiresResource {\n\t\toptions = bindResourceFlags(cmd.Flags())\n\t}\n\n\t// Bind flags hook: each plugin binds to a temporary FlagSet, then we merge into the command so\n\t// duplicate names do not panic; values are synced after parse and help text is aggregated.\n\tduplicateValues := make(map[string][]pflag.Value)\n\tfirstPluginByFlag := make(map[string]string)\n\tfor _, tuple := range subcommands {\n\t\tif subcommand, hasFlags := tuple.subcommand.(plugin.HasFlags); hasFlags {\n\t\t\ttmpSet := pflag.NewFlagSet(cmd.Name(), pflag.ExitOnError)\n\t\t\tsubcommand.BindFlags(tmpSet)\n\t\t\tif err := mergeFlagSetInto(cmd.Flags(), tmpSet, duplicateValues, tuple.key, firstPluginByFlag); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &initHooksResult{options: options, duplicateFlagValues: duplicateValues}, nil\n}\n\ntype executionHooksFactory struct {\n\t// fs is the filesystem abstraction to scaffold files to.\n\tfs machinery.Filesystem\n\t// store is the backend used to load/save the project configuration.\n\tstore store.Store\n\t// subcommands are the tuples representing the set of subcommands provided by the resolved plugins.\n\tsubcommands []keySubcommandTuple\n\t// errorMessage is prepended to returned errors.\n\terrorMessage string\n\t// projectVersion is the project version that will be used to create new project configurations.\n\t// It is only used for initialization.\n\tprojectVersion config.Version\n\t// pluginChain is the plugin chain configured for this project.\n\tpluginChain []string\n\t// cliVersion is the version of the CLI.\n\tcliVersion string\n\t// duplicateFlagValues maps flag names to Values to sync from the parsed flag in PreRunE.\n\tduplicateFlagValues map[string][]pflag.Value\n}\n\nfunc (factory *executionHooksFactory) forEach(cb func(subcommand plugin.Subcommand) error, errorMessage string) error {\n\tfor i, tuple := range factory.subcommands {\n\t\tif tuple.skip {\n\t\t\tcontinue\n\t\t}\n\n\t\terr := factory.withPluginChain(tuple, func() error {\n\t\t\treturn cb(tuple.subcommand)\n\t\t})\n\n\t\tvar exitError plugin.ExitError\n\t\tswitch {\n\t\tcase err == nil:\n\t\t\t// No error do nothing\n\t\tcase errors.As(err, &exitError):\n\t\t\t// Exit errors imply that no further hooks of this subcommand should be called, so we flag it to be skipped\n\t\t\tfactory.subcommands[i].skip = true\n\t\t\tfmt.Printf(\"skipping remaining hooks of %q: %s\\n\", tuple.key, exitError.Reason)\n\t\tdefault:\n\t\t\t// Any other error, wrap it\n\t\t\treturn fmt.Errorf(\"%s: %s %q: %w\", factory.errorMessage, errorMessage, tuple.key, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (factory *executionHooksFactory) withPluginChain(tuple keySubcommandTuple, cb func() error) (err error) {\n\tif tuple.configKey == \"\" {\n\t\treturn cb()\n\t}\n\n\tcfg := factory.store.Config()\n\tif cfg == nil {\n\t\treturn cb()\n\t}\n\n\t// Temporarily move configKey to the front so GetPluginKeyForConfig finds it first.\n\t// This ensures each bundled plugin saves config under the right key.\n\toriginal := append([]string(nil), cfg.GetPluginChain()...)\n\tnewChain := moveKeyToFront(original, tuple.configKey)\n\tchanged := !equalStringSlices(original, newChain)\n\tif changed {\n\t\tif setErr := cfg.SetPluginChain(newChain); setErr != nil {\n\t\t\treturn fmt.Errorf(\"failed to set plugin chain for %q: %w\", tuple.configKey, setErr)\n\t\t}\n\t\tdefer func() {\n\t\t\tif resetErr := cfg.SetPluginChain(original); resetErr != nil && err == nil {\n\t\t\t\terr = fmt.Errorf(\"failed to reset plugin chain: %w\", resetErr)\n\t\t\t}\n\t\t}()\n\t}\n\n\treturn cb()\n}\n\nfunc moveKeyToFront(chain []string, key string) []string {\n\tif len(chain) == 0 {\n\t\treturn []string{key}\n\t}\n\n\tif chain[0] == key {\n\t\treturn chain\n\t}\n\n\tnewChain := make([]string, 0, len(chain)+1)\n\tnewChain = append(newChain, key)\n\tfor _, existing := range chain {\n\t\tif existing == key {\n\t\t\tcontinue\n\t\t}\n\t\tnewChain = append(newChain, existing)\n\t}\n\treturn newChain\n}\n\nfunc equalStringSlices(a, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor i := range a {\n\t\tif a[i] != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// preRunEFunc returns a cobra RunE function that loads the configuration, creates the resource,\n// and executes inject config, inject resource, and pre-scaffold hooks.\nfunc (factory *executionHooksFactory) preRunEFunc(\n\toptions *resourceOptions,\n\tcreateConfig bool,\n) func(*cobra.Command, []string) error {\n\treturn func(cmd *cobra.Command, _ []string) error {\n\t\tif len(factory.duplicateFlagValues) > 0 {\n\t\t\tsyncDuplicateFlags(cmd.Flags(), factory.duplicateFlagValues)\n\t\t}\n\t\tif createConfig {\n\t\t\t// Check if a project configuration is already present.\n\t\t\tif err := factory.store.Load(); err == nil || !errors.Is(err, os.ErrNotExist) {\n\t\t\t\treturn fmt.Errorf(\"%s: already initialized\", factory.errorMessage)\n\t\t\t}\n\n\t\t\t// Initialize the project configuration.\n\t\t\tif err := factory.store.New(factory.projectVersion); err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s: error initializing project configuration: %w\", factory.errorMessage, err)\n\t\t\t}\n\t\t} else {\n\t\t\t// Load the project configuration.\n\t\t\tif err := factory.store.Load(); os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"%s: failed to find configuration file, project must be initialized\",\n\t\t\t\t\tfactory.errorMessage)\n\t\t\t} else if err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s: failed to load configuration file: %w\", factory.errorMessage, err)\n\t\t\t}\n\t\t}\n\t\tcfg := factory.store.Config()\n\n\t\t// Set the CLI version if creating a new project configuration.\n\t\tif createConfig {\n\t\t\t_ = cfg.SetCliVersion(factory.cliVersion)\n\t\t}\n\n\t\t// Set the pluginChain field.\n\t\tif len(factory.pluginChain) != 0 {\n\t\t\t_ = cfg.SetPluginChain(factory.pluginChain)\n\t\t}\n\n\t\t// Create the resource if non-nil options provided\n\t\tvar res *resource.Resource\n\t\tif options != nil {\n\t\t\t// TODO: offer a flag instead of hard-coding project-wide domain\n\t\t\toptions.Domain = cfg.GetDomain()\n\t\t\tif err := options.validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s: failed to create resource: %w\", factory.errorMessage, err)\n\t\t\t}\n\t\t\tres = options.newResource()\n\t\t}\n\n\t\t// Inject config hook.\n\t\tif err := factory.forEach(func(subcommand plugin.Subcommand) error {\n\t\t\tif subcommand, requiresConfig := subcommand.(plugin.RequiresConfig); requiresConfig {\n\t\t\t\treturn subcommand.InjectConfig(cfg)\n\t\t\t}\n\t\t\treturn nil\n\t\t}, \"unable to inject the configuration to\"); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif res != nil {\n\t\t\t// Inject resource hook.\n\t\t\tif err := factory.forEach(func(subcommand plugin.Subcommand) error {\n\t\t\t\tif subcommand, requiresResource := subcommand.(plugin.RequiresResource); requiresResource {\n\t\t\t\t\treturn subcommand.InjectResource(res)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}, \"unable to inject the resource to\"); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif err := res.Validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s: created invalid resource: %w\", factory.errorMessage, err)\n\t\t\t}\n\t\t}\n\n\t\t// Pre-scaffold hook.\n\t\t//nolint:revive\n\t\tif err := factory.forEach(func(subcommand plugin.Subcommand) error {\n\t\t\tif subcommand, hasPreScaffold := subcommand.(plugin.HasPreScaffold); hasPreScaffold {\n\t\t\t\treturn subcommand.PreScaffold(factory.fs)\n\t\t\t}\n\t\t\treturn nil\n\t\t}, \"unable to run pre-scaffold tasks of\"); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// runEFunc returns a cobra RunE function that executes the scaffold hook.\nfunc (factory *executionHooksFactory) runEFunc() func(*cobra.Command, []string) error {\n\treturn func(*cobra.Command, []string) error {\n\t\t// Scaffold hook.\n\t\t//nolint:revive\n\t\tif err := factory.forEach(func(subcommand plugin.Subcommand) error {\n\t\t\treturn subcommand.Scaffold(factory.fs)\n\t\t}, \"unable to scaffold with\"); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// postRunEFunc returns a cobra RunE function that saves the configuration\n// and executes the post-scaffold hook.\nfunc (factory *executionHooksFactory) postRunEFunc() func(*cobra.Command, []string) error {\n\treturn func(*cobra.Command, []string) error {\n\t\tif err := factory.store.Save(); err != nil {\n\t\t\treturn fmt.Errorf(\"%s: failed to save configuration file: %w\", factory.errorMessage, err)\n\t\t}\n\n\t\t// Post-scaffold hook.\n\t\t//nolint:revive\n\t\tif err := factory.forEach(func(subcommand plugin.Subcommand) error {\n\t\t\tif subcommand, hasPostScaffold := subcommand.(plugin.HasPostScaffold); hasPostScaffold {\n\t\t\t\treturn subcommand.PostScaffold()\n\t\t\t}\n\t\t\treturn nil\n\t\t}, \"unable to run post-scaffold tasks of\"); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/cli/cmd_helpers_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar _ = Describe(\"cmd_helpers\", func() {\n\tContext(\"error types\", func() {\n\t\tIt(\"noResolvedPluginError should return correct message\", func() {\n\t\t\terr := noResolvedPluginError{}\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"no resolved plugin\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"verify the project version and plugins\"))\n\t\t})\n\n\t\tIt(\"noAvailablePluginError should return correct message with subcommand\", func() {\n\t\t\terr := noAvailablePluginError{subcommand: \"init\"}\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"init\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"do not provide any\"))\n\t\t})\n\t})\n\n\tContext(\"cmdErr\", func() {\n\t\tIt(\"should update command with error information\", func() {\n\t\t\tcmd := &cobra.Command{\n\t\t\t\tLong: \"Original description\",\n\t\t\t\tRunE: func(*cobra.Command, []string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t}\n\t\t\ttestError := errors.New(\"test error\")\n\n\t\t\tcmdErr(cmd, testError)\n\n\t\t\tExpect(cmd.Long).To(ContainSubstring(\"Original description\"))\n\t\t\tExpect(cmd.Long).To(ContainSubstring(\"test error\"))\n\t\t\tExpect(cmd.RunE).NotTo(BeNil())\n\n\t\t\terr := cmd.RunE(cmd, []string{})\n\t\t\tExpect(err).To(Equal(testError))\n\t\t})\n\t})\n\n\tContext(\"errCmdFunc\", func() {\n\t\tIt(\"should return a function that returns the provided error\", func() {\n\t\t\ttestError := errors.New(\"test error\")\n\t\t\trunE := errCmdFunc(testError)\n\n\t\t\terr := runE(nil, nil)\n\t\t\tExpect(err).To(Equal(testError))\n\t\t})\n\t})\n\n\tContext(\"moveKeyToFront\", func() {\n\t\tIt(\"should handle empty chain\", func() {\n\t\t\tresult := moveKeyToFront([]string{}, \"key1\")\n\t\t\tExpect(result).To(Equal([]string{\"key1\"}))\n\t\t})\n\n\t\tIt(\"should not change chain when key is already at front\", func() {\n\t\t\tchain := []string{\"key1\", \"key2\", \"key3\"}\n\t\t\tresult := moveKeyToFront(chain, \"key1\")\n\t\t\tExpect(result).To(Equal(chain))\n\t\t})\n\n\t\tIt(\"should move key to front when it exists in chain\", func() {\n\t\t\tchain := []string{\"key1\", \"key2\", \"key3\"}\n\t\t\tresult := moveKeyToFront(chain, \"key2\")\n\t\t\tExpect(result).To(Equal([]string{\"key2\", \"key1\", \"key3\"}))\n\t\t})\n\n\t\tIt(\"should move key to front from end of chain\", func() {\n\t\t\tchain := []string{\"key1\", \"key2\", \"key3\"}\n\t\t\tresult := moveKeyToFront(chain, \"key3\")\n\t\t\tExpect(result).To(Equal([]string{\"key3\", \"key1\", \"key2\"}))\n\t\t})\n\n\t\tIt(\"should add key to front when not in chain\", func() {\n\t\t\tchain := []string{\"key1\", \"key2\"}\n\t\t\tresult := moveKeyToFront(chain, \"key3\")\n\t\t\tExpect(result).To(Equal([]string{\"key3\", \"key1\", \"key2\"}))\n\t\t})\n\n\t\tIt(\"should remove duplicate when moving key to front\", func() {\n\t\t\tchain := []string{\"key1\", \"key2\", \"key2\"}\n\t\t\tresult := moveKeyToFront(chain, \"key2\")\n\t\t\tExpect(result).To(Equal([]string{\"key2\", \"key1\"}))\n\t\t})\n\t})\n\n\tContext(\"equalStringSlices\", func() {\n\t\tIt(\"should return true for equal slices\", func() {\n\t\t\ta := []string{\"a\", \"b\", \"c\"}\n\t\t\tb := []string{\"a\", \"b\", \"c\"}\n\t\t\tExpect(equalStringSlices(a, b)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return true for empty slices\", func() {\n\t\t\tExpect(equalStringSlices([]string{}, []string{})).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return false for different lengths\", func() {\n\t\t\ta := []string{\"a\", \"b\"}\n\t\t\tb := []string{\"a\", \"b\", \"c\"}\n\t\t\tExpect(equalStringSlices(a, b)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false for different content\", func() {\n\t\t\ta := []string{\"a\", \"b\", \"c\"}\n\t\t\tb := []string{\"a\", \"x\", \"c\"}\n\t\t\tExpect(equalStringSlices(a, b)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false for different order\", func() {\n\t\t\ta := []string{\"a\", \"b\", \"c\"}\n\t\t\tb := []string{\"c\", \"b\", \"a\"}\n\t\t\tExpect(equalStringSlices(a, b)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should handle nil slices\", func() {\n\t\t\tvar a, b []string\n\t\t\tExpect(equalStringSlices(a, b)).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"collectSubcommands\", func() {\n\t\tvar (\n\t\t\ttestPlugin       *mockPluginWithSubcommand\n\t\t\ttestSubcommand   *mockTestSubcommand\n\t\t\ttestBundle       *mockPluginBundle\n\t\t\ttestNestedPlugin *mockPluginWithSubcommand\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\ttestSubcommand = &mockTestSubcommand{}\n\t\t\ttestPlugin = newMockPluginWithSubcommand(\n\t\t\t\t\"test.plugin\", []config.Version{{Number: 1}}, testSubcommand)\n\t\t\ttestNestedPlugin = newMockPluginWithSubcommand(\n\t\t\t\t\"nested.plugin\", []config.Version{{Number: 1}}, &mockTestSubcommand{})\n\t\t\ttestBundle = newMockPluginBundle(\n\t\t\t\t\"test.bundle\", []config.Version{{Number: 1}}, []plugin.Plugin{testNestedPlugin})\n\t\t})\n\n\t\tIt(\"should return nil when filter returns false\", func() {\n\t\t\tfilter := func(plugin.Plugin) bool { return false }\n\t\t\textract := func(plugin.Plugin) plugin.Subcommand { return testSubcommand }\n\n\t\t\tresult := collectSubcommands(testPlugin, \"config.key\", filter, extract)\n\t\t\tExpect(result).To(BeNil())\n\t\t})\n\n\t\tIt(\"should collect subcommand from single plugin\", func() {\n\t\t\tfilter := func(plugin.Plugin) bool { return true }\n\t\t\textract := func(p plugin.Plugin) plugin.Subcommand {\n\t\t\t\tif mp, ok := p.(*mockPluginWithSubcommand); ok {\n\t\t\t\t\treturn mp.subcommand\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tresult := collectSubcommands(testPlugin, \"config.key\", filter, extract)\n\t\t\tExpect(result).To(HaveLen(1))\n\t\t\tExpect(result[0].key).To(Equal(\"test.plugin/v1\"))\n\t\t\tExpect(result[0].configKey).To(Equal(\"config.key\"))\n\t\t\tExpect(result[0].subcommand).To(Equal(testSubcommand))\n\t\t})\n\n\t\tIt(\"should collect subcommands from bundle\", func() {\n\t\t\tfilter := func(plugin.Plugin) bool { return true }\n\t\t\textract := func(p plugin.Plugin) plugin.Subcommand {\n\t\t\t\tif mp, ok := p.(*mockPluginWithSubcommand); ok {\n\t\t\t\t\treturn mp.subcommand\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tresult := collectSubcommands(testBundle, \"bundle.key\", filter, extract)\n\t\t\tExpect(result).To(HaveLen(1))\n\t\t\tExpect(result[0].key).To(Equal(\"nested.plugin/v1\"))\n\t\t\tExpect(result[0].configKey).To(Equal(\"bundle.key\"))\n\t\t})\n\t})\n\n\tContext(\"filterSubcommands\", func() {\n\t\tvar (\n\t\t\tcli            *CLI\n\t\t\ttestPlugin1    *mockPluginWithSubcommand\n\t\t\ttestPlugin2    *mockPluginWithSubcommand\n\t\t\ttestSubcommand *mockTestSubcommand\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\ttestSubcommand = &mockTestSubcommand{}\n\t\t\ttestPlugin1 = newMockPluginWithSubcommand(\"plugin1\", []config.Version{{Number: 1}}, testSubcommand)\n\t\t\ttestPlugin2 = newMockPluginWithSubcommand(\"plugin2\", []config.Version{{Number: 1}}, testSubcommand)\n\n\t\t\tcli = &CLI{\n\t\t\t\tresolvedPlugins: []plugin.Plugin{testPlugin1, testPlugin2},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should filter and extract subcommands from all plugins\", func() {\n\t\t\tfilter := func(p plugin.Plugin) bool {\n\t\t\t\treturn p.Name() == \"plugin1\"\n\t\t\t}\n\t\t\textract := func(p plugin.Plugin) plugin.Subcommand {\n\t\t\t\tif mp, ok := p.(*mockPluginWithSubcommand); ok {\n\t\t\t\t\treturn mp.subcommand\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tresult := cli.filterSubcommands(filter, extract)\n\t\t\tExpect(result).To(HaveLen(1))\n\t\t\tExpect(result[0].key).To(Equal(\"plugin1/v1\"))\n\t\t})\n\n\t\tIt(\"should return all subcommands when filter allows all\", func() {\n\t\t\tfilter := func(plugin.Plugin) bool { return true }\n\t\t\textract := func(p plugin.Plugin) plugin.Subcommand {\n\t\t\t\tif mp, ok := p.(*mockPluginWithSubcommand); ok {\n\t\t\t\t\treturn mp.subcommand\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tresult := cli.filterSubcommands(filter, extract)\n\t\t\tExpect(result).To(HaveLen(2))\n\t\t})\n\n\t\tIt(\"should return empty when filter rejects all\", func() {\n\t\t\tfilter := func(plugin.Plugin) bool { return false }\n\t\t\textract := func(plugin.Plugin) plugin.Subcommand { return testSubcommand }\n\n\t\t\tresult := cli.filterSubcommands(filter, extract)\n\t\t\tExpect(result).To(BeEmpty())\n\t\t})\n\t})\n\n\tContext(\"duplicate flag handling (mergeFlagSetInto, syncDuplicateFlags)\", func() {\n\t\tIt(\"should not panic when merging two FlagSets that define the same flag name (same type)\", func() {\n\t\t\tdest := pflag.NewFlagSet(\"dest\", pflag.ExitOnError)\n\t\t\tsrc := pflag.NewFlagSet(\"src\", pflag.ExitOnError)\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\n\t\t\tvar destBool bool\n\t\t\tvar srcBool bool\n\t\t\tdest.BoolVar(&destBool, \"force\", false, \"overwrite files (plugin A)\")\n\t\t\tsrc.BoolVar(&srcBool, \"force\", false, \"regenerate all files (plugin B)\")\n\n\t\t\terr := mergeFlagSetInto(dest, src, duplicateValues, \"pluginB/v1\", firstPluginByFlag)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(dest.Lookup(\"force\")).NotTo(BeNil())\n\t\t\tExpect(duplicateValues[\"force\"]).To(HaveLen(1))\n\t\t})\n\n\t\tIt(\"should aggregate help text as For plugin (key): desc AND for plugin (key): desc\", func() {\n\t\t\tdest := pflag.NewFlagSet(\"dest\", pflag.ExitOnError)\n\t\t\tsrc := pflag.NewFlagSet(\"src\", pflag.ExitOnError)\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\n\t\t\tvar a, b bool\n\t\t\tdest.BoolVar(&a, \"force\", false, \"overwrite files (plugin A)\")\n\t\t\tsrc.BoolVar(&b, \"force\", false, \"regenerate all files (plugin B)\")\n\n\t\t\terr := mergeFlagSetInto(dest, src, duplicateValues, \"pluginB/v1\", firstPluginByFlag)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tflag := dest.Lookup(\"force\")\n\t\t\tExpect(flag).NotTo(BeNil())\n\t\t\tExpect(flag.Usage).To(ContainSubstring(\"overwrite files (plugin A)\"))\n\t\t\tExpect(flag.Usage).To(ContainSubstring(\"AND for plugin (pluginB/v1):\"))\n\t\t\tExpect(flag.Usage).To(ContainSubstring(\"regenerate all files (plugin B)\"))\n\t\t})\n\n\t\tIt(\"should prefix first plugin with For plugin (key): when both flags merged via mergeFlagSetInto\", func() {\n\t\t\tdest := pflag.NewFlagSet(\"dest\", pflag.ExitOnError)\n\t\t\tpluginA := pflag.NewFlagSet(\"a\", pflag.ExitOnError)\n\t\t\tpluginB := pflag.NewFlagSet(\"b\", pflag.ExitOnError)\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\n\t\t\tvar a, b bool\n\t\t\tpluginA.BoolVar(&a, \"force\", false, \"overwrite files (plugin A)\")\n\t\t\tpluginB.BoolVar(&b, \"force\", false, \"regenerate all files (plugin B)\")\n\n\t\t\tExpect(mergeFlagSetInto(dest, pluginA, duplicateValues, \"pluginA/v1\", firstPluginByFlag)).NotTo(HaveOccurred())\n\t\t\tExpect(mergeFlagSetInto(dest, pluginB, duplicateValues, \"pluginB/v1\", firstPluginByFlag)).NotTo(HaveOccurred())\n\n\t\t\tflag := dest.Lookup(\"force\")\n\t\t\tExpect(flag).NotTo(BeNil())\n\t\t\tExpect(flag.Usage).To(Equal(\n\t\t\t\t\"For plugin (pluginA/v1): overwrite files (plugin A) AND for plugin (pluginB/v1): regenerate all files (plugin B)\"))\n\t\t})\n\n\t\tIt(\"should show full plugin keys in aggregated usage\", func() {\n\t\t\tdest := pflag.NewFlagSet(\"dest\", pflag.ExitOnError)\n\t\t\tgoPlugin := pflag.NewFlagSet(\"go\", pflag.ExitOnError)\n\t\t\thelmPlugin := pflag.NewFlagSet(\"helm\", pflag.ExitOnError)\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\n\t\t\tvar a, b bool\n\t\t\tgoPlugin.BoolVar(&a, \"force\", false, \"overwrite scaffolded files to apply changes (manual edits may be lost)\")\n\t\t\thelmPlugin.BoolVar(&b, \"force\", false, \"if true, regenerates all the files\")\n\n\t\t\tExpect(mergeFlagSetInto(dest, goPlugin, duplicateValues, \"base.go.kubebuilder.io/v4\", firstPluginByFlag)).\n\t\t\t\tNotTo(HaveOccurred())\n\t\t\tExpect(mergeFlagSetInto(dest, helmPlugin, duplicateValues, \"helm.kubebuilder.io/v2-alpha\", firstPluginByFlag)).\n\t\t\t\tNotTo(HaveOccurred())\n\n\t\t\tflag := dest.Lookup(\"force\")\n\t\t\tExpect(flag).NotTo(BeNil())\n\t\t\texpectedUsage := \"For plugin (base.go.kubebuilder.io/v4): overwrite scaffolded files to apply changes \" +\n\t\t\t\t\"(manual edits may be lost) AND for plugin (helm.kubebuilder.io/v2-alpha): if true, regenerates all the files\"\n\t\t\tExpect(flag.Usage).To(Equal(expectedUsage))\n\t\t})\n\n\t\tIt(\"should return error when same flag name is bound with different value types\", func() {\n\t\t\tdest := pflag.NewFlagSet(\"dest\", pflag.ExitOnError)\n\t\t\tsrc := pflag.NewFlagSet(\"src\", pflag.ExitOnError)\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\t\t\tfirstPluginByFlag[\"flag\"] = \"pluginA/v1\" // dest already has this flag from a previous plugin\n\n\t\t\tvar a bool\n\t\t\tvar b string\n\t\t\tdest.BoolVar(&a, \"flag\", false, \"bool usage (plugin A)\")\n\t\t\tsrc.StringVar(&b, \"flag\", \"\", \"string usage (plugin B)\")\n\n\t\t\terr := mergeFlagSetInto(dest, src, duplicateValues, \"pluginB/v1\", firstPluginByFlag)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"same flag name\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"different value types\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"flag\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"bool\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"string\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"pluginA/v1\"))\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"pluginB/v1\"))\n\t\t})\n\n\t\tIt(\"should sync parsed value to duplicate Values after syncDuplicateFlags\", func() {\n\t\t\tflags := pflag.NewFlagSet(\"cmd\", pflag.ExitOnError)\n\t\t\tvar mainVal, dupVal bool\n\t\t\tflags.BoolVar(&mainVal, \"force\", false, \"usage\")\n\t\t\ttmpFS := pflag.NewFlagSet(\"\", pflag.ExitOnError)\n\t\t\ttmpFS.BoolVar(&dupVal, \"force\", false, \"\")\n\t\t\tduplicateValues := map[string][]pflag.Value{\n\t\t\t\t\"force\": {tmpFS.Lookup(\"force\").Value},\n\t\t\t}\n\n\t\t\tExpect(flags.Parse([]string{\"--force\", \"true\"})).NotTo(HaveOccurred())\n\t\t\tExpect(mainVal).To(BeTrue())\n\t\t\tExpect(dupVal).To(BeFalse())\n\n\t\t\tsyncDuplicateFlags(flags, duplicateValues)\n\t\t\tExpect(dupVal).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should give all plugins in the chain the same value for a shared flag (e.g. --force)\", func() {\n\t\t\tcmdFlags := pflag.NewFlagSet(\"edit\", pflag.ExitOnError)\n\t\t\tpluginA := pflag.NewFlagSet(\"pluginA\", pflag.ExitOnError)\n\t\t\tpluginB := pflag.NewFlagSet(\"pluginB\", pflag.ExitOnError)\n\t\t\tvar forceA, forceB bool\n\t\t\tpluginA.BoolVar(&forceA, \"force\", false, \"plugin A force\")\n\t\t\tpluginB.BoolVar(&forceB, \"force\", false, \"plugin B force\")\n\n\t\t\tduplicateValues := make(map[string][]pflag.Value)\n\t\t\tfirstPluginByFlag := make(map[string]string)\n\t\t\tExpect(mergeFlagSetInto(cmdFlags, pluginA, duplicateValues, \"pluginA/v1\", firstPluginByFlag)).NotTo(HaveOccurred())\n\t\t\tExpect(mergeFlagSetInto(cmdFlags, pluginB, duplicateValues, \"pluginB/v1\", firstPluginByFlag)).NotTo(HaveOccurred())\n\n\t\t\tExpect(cmdFlags.Parse([]string{\"--force\", \"true\"})).NotTo(HaveOccurred())\n\t\t\tsyncDuplicateFlags(cmdFlags, duplicateValues)\n\t\t\tExpect(forceA).To(BeTrue(), \"plugin A must receive the value passed by the user\")\n\t\t\tExpect(forceB).To(BeTrue(), \"plugin B must receive the same value as the command\")\n\t\t})\n\n\t\tIt(\"should sync string flag value to duplicate Values\", func() {\n\t\t\tflags := pflag.NewFlagSet(\"cmd\", pflag.ExitOnError)\n\t\t\tvar mainVal, dupVal string\n\t\t\tflags.StringVar(&mainVal, \"name\", \"\", \"name usage\")\n\t\t\ttmpFS := pflag.NewFlagSet(\"\", pflag.ExitOnError)\n\t\t\ttmpFS.StringVar(&dupVal, \"name\", \"\", \"\")\n\t\t\tduplicateValues := map[string][]pflag.Value{\n\t\t\t\t\"name\": {tmpFS.Lookup(\"name\").Value},\n\t\t\t}\n\n\t\t\tExpect(flags.Parse([]string{\"--name\", \"foo\"})).NotTo(HaveOccurred())\n\t\t\tsyncDuplicateFlags(flags, duplicateValues)\n\t\t\tExpect(dupVal).To(Equal(\"foo\"))\n\t\t})\n\n\t\tIt(\"applies merge and sync for any subcommand (init, api, webhook, edit), not only edit\", func() {\n\t\t\tcmd := &cobra.Command{Use: \"api\"}\n\t\t\tpluginA := &mockSubcommandWithForceFlag{}\n\t\t\tpluginB := &mockSubcommandWithForceFlag{}\n\t\t\ttuples := []keySubcommandTuple{\n\t\t\t\t{key: \"pluginA.kubebuilder.io/v1\", subcommand: pluginA},\n\t\t\t\t{key: \"pluginB.kubebuilder.io/v1\", subcommand: pluginB},\n\t\t\t}\n\t\t\tmeta := plugin.CLIMetadata{}\n\n\t\t\tresult, err := initializationHooks(cmd, tuples, meta)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.duplicateFlagValues[\"force\"]).To(HaveLen(1), \"second plugin's Value recorded as duplicate\")\n\n\t\t\tExpect(cmd.ParseFlags([]string{\"--force\", \"true\"})).NotTo(HaveOccurred())\n\t\t\tsyncDuplicateFlags(cmd.Flags(), result.duplicateFlagValues)\n\t\t\tExpect(pluginA.Force).To(BeTrue(), \"first plugin (flag on command) receives value\")\n\t\t\tExpect(pluginB.Force).To(BeTrue(), \"second plugin (duplicate) receives same value after sync\")\n\t\t})\n\t})\n})\n\ntype mockTestSubcommand struct{}\n\nfunc (m *mockTestSubcommand) Scaffold(machinery.Filesystem) error {\n\treturn nil\n}\n\n// mockSubcommandWithForceFlag implements Subcommand and HasFlags for tests with a shared flag.\ntype mockSubcommandWithForceFlag struct {\n\tForce bool\n}\n\nfunc (m *mockSubcommandWithForceFlag) Scaffold(machinery.Filesystem) error {\n\treturn nil\n}\n\nfunc (m *mockSubcommandWithForceFlag) BindFlags(flags *pflag.FlagSet) {\n\tflags.BoolVar(&m.Force, \"force\", false, \"force usage\")\n}\n\ntype mockPluginWithSubcommand struct {\n\tname                     string\n\tsupportedProjectVersions []config.Version\n\tsubcommand               plugin.Subcommand\n}\n\nfunc newMockPluginWithSubcommand(\n\tname string,\n\tversions []config.Version,\n\tsubcommand plugin.Subcommand,\n) *mockPluginWithSubcommand {\n\treturn &mockPluginWithSubcommand{\n\t\tname:                     name,\n\t\tsupportedProjectVersions: versions,\n\t\tsubcommand:               subcommand,\n\t}\n}\n\nfunc (m *mockPluginWithSubcommand) Name() string {\n\treturn m.name\n}\n\nfunc (m *mockPluginWithSubcommand) Version() plugin.Version {\n\treturn plugin.Version{Number: 1}\n}\n\nfunc (m *mockPluginWithSubcommand) SupportedProjectVersions() []config.Version {\n\treturn m.supportedProjectVersions\n}\n\ntype mockPluginBundle struct {\n\tname                     string\n\tsupportedProjectVersions []config.Version\n\tplugins                  []plugin.Plugin\n}\n\nfunc newMockPluginBundle(name string, versions []config.Version, plugins []plugin.Plugin) *mockPluginBundle {\n\treturn &mockPluginBundle{\n\t\tname:                     name,\n\t\tsupportedProjectVersions: versions,\n\t\tplugins:                  plugins,\n\t}\n}\n\nfunc (m *mockPluginBundle) Name() string {\n\treturn m.name\n}\n\nfunc (m *mockPluginBundle) Version() plugin.Version {\n\treturn plugin.Version{Number: 1}\n}\n\nfunc (m *mockPluginBundle) SupportedProjectVersions() []config.Version {\n\treturn m.supportedProjectVersions\n}\n\nfunc (m *mockPluginBundle) Plugins() []plugin.Plugin {\n\treturn m.plugins\n}\n"
  },
  {
    "path": "pkg/cli/completion.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (c CLI) newBashCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"bash\",\n\t\tShort: \"Load bash completions\",\n\t\tExample: fmt.Sprintf(`# To load completion for this session, execute:\n$ source <(%[1]s completion bash)\n\n# To load completions for each session, execute once:\nLinux:\n  $ %[1]s completion bash > /etc/bash_completion.d/%[1]s\nMacOS:\n  $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s\n`, c.commandName),\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\treturn cmd.Root().GenBashCompletionV2(os.Stdout, true)\n\t\t},\n\t}\n}\n\nfunc (c CLI) newZshCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"zsh\",\n\t\tShort: \"Load zsh completions\",\n\t\tExample: fmt.Sprintf(`# If shell completion is not already enabled in your environment you will need\n# to enable it. You can execute the following once:\n$ echo \"autoload -U compinit; compinit\" >> ~/.zshrc\n\n# To load completions for each session, execute once:\n$ %[1]s completion zsh > \"${fpath[1]}/_%[1]s\"\n\n# You will need to start a new shell for this setup to take effect.\n`, c.commandName),\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\treturn cmd.Root().GenZshCompletion(os.Stdout)\n\t\t},\n\t}\n}\n\nfunc (c CLI) newFishCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"fish\",\n\t\tShort: \"Load fish completions\",\n\t\tExample: fmt.Sprintf(`# To load completion for this session, execute:\n$ %[1]s completion fish | source\n\n# To load completions for each session, execute once:\n$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish\n`, c.commandName),\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\treturn cmd.Root().GenFishCompletion(os.Stdout, true)\n\t\t},\n\t}\n}\n\nfunc (CLI) newPowerShellCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"powershell\",\n\t\tShort: \"Load powershell completions\",\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\treturn cmd.Root().GenPowerShellCompletion(os.Stdout)\n\t\t},\n\t}\n}\n\nfunc (c CLI) newCompletionCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"completion\",\n\t\tShort: \"Load completions for the specified shell\",\n\t\tLong: fmt.Sprintf(`Output shell completion code for the specified shell.\nThe shell code must be evaluated to provide interactive completion of %[1]s commands.\nDetailed instructions on how to do this for each shell are provided in their own commands.\n`, c.commandName),\n\t}\n\tcmd.AddCommand(c.newBashCmd())\n\tcmd.AddCommand(c.newZshCmd())\n\tcmd.AddCommand(c.newFishCmd())\n\tcmd.AddCommand(c.newPowerShellCmd())\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cli/completion_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Completion\", func() {\n\tvar c *CLI\n\n\tBeforeEach(func() {\n\t\tc = &CLI{}\n\t})\n\n\tWhen(\"newBashCmd\", func() {\n\t\tIt(\"Testing the BashCompletion\", func() {\n\t\t\tcmd := c.newBashCmd()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"bash\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Load bash completions\"))\n\t\t\tExpect(cmd.Example).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Example).To(ContainSubstring(\"# To load completion for this session\"))\n\t\t})\n\t})\n\n\tContext(\"newZshCmd\", func() {\n\t\tIt(\"Testing the ZshCompletion\", func() {\n\t\t\tcmd := c.newZshCmd()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"zsh\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Load zsh completions\"))\n\t\t\tExpect(cmd.Example).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Example).To(ContainSubstring(\"# If shell completion is not already enabled in your environment\"))\n\t\t})\n\t})\n\n\tContext(\"newFishCmd\", func() {\n\t\tIt(\"Testing the FishCompletion\", func() {\n\t\t\tcmd := c.newFishCmd()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"fish\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Load fish completions\"))\n\t\t\tExpect(cmd.Example).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Example).To(ContainSubstring(\"# To load completion for this session, execute:\"))\n\t\t})\n\t})\n\n\tContext(\"newPowerShellCmd\", func() {\n\t\tIt(\"Testing the PowerShellCompletion\", func() {\n\t\t\tcmd := c.newPowerShellCmd()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"powershell\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Load powershell completions\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/create.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nfunc (c CLI) newCreateCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:        \"create\",\n\t\tSuggestFor: []string{\"new\"},\n\t\tShort:      \"Scaffold a Kubernetes API or webhook\",\n\t\tLong: fmt.Sprintf(`Scaffold a Kubernetes API or webhook.\n\nAvailable plugins that support 'create' subcommands:\n\n%s\n`, c.getPluginTableFilteredForSubcommand(func(p plugin.Plugin) bool {\n\t\t\t_, hasCreateAPI := p.(plugin.CreateAPI)\n\t\t\t_, hasCreateWebhook := p.(plugin.CreateWebhook)\n\t\t\treturn hasCreateAPI || hasCreateWebhook\n\t\t})),\n\t}\n}\n"
  },
  {
    "path": "pkg/cli/doc.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package cli provides the required tools to build a CLI utility that creates\n// scaffolds for operator projects.\n//\n// It is the entrypoint for any CLI that wants to use kubebuilder's scaffolding\n// capabilities.\npackage cli\n"
  },
  {
    "path": "pkg/cli/edit.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nconst editErrorMsg = \"failed to edit project\"\n\nfunc (c CLI) newEditCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"edit\",\n\t\tShort: \"Update the project configuration\",\n\t\tLong:  `Edit the project configuration.`,\n\t\tRunE: errCmdFunc(\n\t\t\tfmt.Errorf(\"project must be initialized\"),\n\t\t),\n\t}\n\n\t// In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of\n\t// this subcommand. This allows the use of subcommands that do not require resolved plugins like help.\n\tif len(c.resolvedPlugins) == 0 {\n\t\tcmdErr(cmd, noResolvedPluginError{})\n\t\treturn cmd\n\t}\n\n\t// Obtain the plugin keys and subcommands from the plugins that implement plugin.Edit.\n\tsubcommands := c.filterSubcommands(\n\t\tfunc(p plugin.Plugin) bool {\n\t\t\t_, isValid := p.(plugin.Edit)\n\t\t\treturn isValid\n\t\t},\n\t\tfunc(p plugin.Plugin) plugin.Subcommand {\n\t\t\treturn p.(plugin.Edit).GetEditSubcommand()\n\t\t},\n\t)\n\n\t// Verify that there is at least one remaining plugin.\n\tif len(subcommands) == 0 {\n\t\tcmdErr(cmd, noAvailablePluginError{\"edit project\"})\n\t\treturn cmd\n\t}\n\n\tc.applySubcommandHooks(cmd, subcommands, editErrorMsg, false)\n\n\t// Append plugin table after metadata updates\n\tc.appendPluginTable(cmd, func(p plugin.Plugin) bool {\n\t\t_, isValid := p.(plugin.Edit)\n\t\treturn isValid\n\t}, \"Available plugins that support 'edit'\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cli/init.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nconst initErrorMsg = \"failed to initialize project\"\n\nfunc (c CLI) newInitCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"init\",\n\t\tShort: \"Initialize a new project\",\n\t\tLong: `Initialize a new project.\n\nFor further help about a specific plugin, set --plugins.\n`,\n\t\tExample: c.getInitHelpExamples(),\n\t\tRun:     func(_ *cobra.Command, _ []string) {},\n\t}\n\n\t// Register --project-version on the dynamically created command\n\t// so that it shows up in help and does not cause a parse error.\n\tcmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), \"project version\")\n\n\t// In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of\n\t// this subcommand. This allows the use of subcommands that do not require resolved plugins like help.\n\tif len(c.resolvedPlugins) == 0 {\n\t\tcmdErr(cmd, noResolvedPluginError{})\n\t\treturn cmd\n\t}\n\n\t// Obtain the plugin keys and subcommands from the plugins that implement plugin.Init.\n\tsubcommands := c.filterSubcommands(\n\t\tfunc(p plugin.Plugin) bool {\n\t\t\t_, isValid := p.(plugin.Init)\n\t\t\treturn isValid\n\t\t},\n\t\tfunc(p plugin.Plugin) plugin.Subcommand {\n\t\t\treturn p.(plugin.Init).GetInitSubcommand()\n\t\t},\n\t)\n\n\t// Verify that there is at least one remaining plugin.\n\tif len(subcommands) == 0 {\n\t\tcmdErr(cmd, noAvailablePluginError{\"project initialization\"})\n\t\treturn cmd\n\t}\n\n\tc.applySubcommandHooks(cmd, subcommands, initErrorMsg, true)\n\n\t// Append plugin table after metadata updates\n\tc.appendPluginTable(cmd, func(p plugin.Plugin) bool {\n\t\t_, isValid := p.(plugin.Init)\n\t\treturn isValid\n\t}, \"Available plugins that support 'init'\")\n\n\treturn cmd\n}\n\nfunc (c CLI) getInitHelpExamples() string {\n\tvar sb strings.Builder\n\tfor _, version := range c.getAvailableProjectVersions() {\n\t\trendered := fmt.Sprintf(`  # Help for initializing a project with version %[2]s\n  %[1]s init --project-version=%[2]s -h\n\n`,\n\t\t\tc.commandName, version)\n\t\tsb.WriteString(rendered)\n\t}\n\treturn strings.TrimSuffix(sb.String(), \"\\n\\n\")\n}\n\nfunc (c CLI) getAvailableProjectVersions() (projectVersions []string) {\n\tversionSet := make(map[config.Version]struct{})\n\tfor _, p := range c.plugins {\n\t\t// Only return versions of non-deprecated plugins.\n\t\tif _, isDeprecated := p.(plugin.Deprecated); !isDeprecated {\n\t\t\tfor _, version := range p.SupportedProjectVersions() {\n\t\t\t\tversionSet[version] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\tfor version := range versionSet {\n\t\tprojectVersions = append(projectVersions, strconv.Quote(version.String()))\n\t}\n\tslices.Sort(projectVersions)\n\treturn projectVersions\n}\n"
  },
  {
    "path": "pkg/cli/init_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar _ = Describe(\"init\", func() {\n\tContext(\"getInitHelpExamples\", func() {\n\t\tIt(\"should return help examples for available project versions\", func() {\n\t\t\tplugin1 := newMockPlugin(\"test.plugin\", \"3\", config.Version{Number: 3})\n\t\t\tplugin2 := newMockPlugin(\"another.plugin\", \"3\", config.Version{Number: 3}, config.Version{Number: 4})\n\n\t\t\tcli := CLI{\n\t\t\t\tcommandName: \"kubebuilder\",\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(plugin1): plugin1,\n\t\t\t\t\tplugin.KeyFor(plugin2): plugin2,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\texamples := cli.getInitHelpExamples()\n\n\t\t\tExpect(examples).To(ContainSubstring(\"kubebuilder init\"))\n\t\t\tExpect(examples).To(ContainSubstring(\"project-version\"))\n\t\t\tExpect(examples).To(ContainSubstring(\"-h\"))\n\t\t})\n\n\t\tIt(\"should handle multiple versions\", func() {\n\t\t\tplugin1 := newMockPlugin(\"plugin1\", \"3\", config.Version{Number: 2})\n\t\t\tplugin2 := newMockPlugin(\"plugin2\", \"3\", config.Version{Number: 3})\n\n\t\t\tcli := CLI{\n\t\t\t\tcommandName: \"kb\",\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(plugin1): plugin1,\n\t\t\t\t\tplugin.KeyFor(plugin2): plugin2,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\texamples := cli.getInitHelpExamples()\n\n\t\t\tExpect(examples).To(ContainSubstring(\"kb init\"))\n\t\t\tExpect(examples).NotTo(HaveSuffix(\"\\n\\n\"))\n\t\t})\n\n\t\tIt(\"should return empty string when no plugins\", func() {\n\t\t\tcli := CLI{\n\t\t\t\tcommandName: \"kubebuilder\",\n\t\t\t\tplugins:     map[string]plugin.Plugin{},\n\t\t\t}\n\n\t\t\texamples := cli.getInitHelpExamples()\n\t\t\tExpect(examples).To(BeEmpty())\n\t\t})\n\t})\n\n\tContext(\"getAvailableProjectVersions\", func() {\n\t\tIt(\"should return unique project versions\", func() {\n\t\t\tplugin1 := newMockPlugin(\"plugin1\", \"3\", config.Version{Number: 3})\n\t\t\tplugin2 := newMockPlugin(\"plugin2\", \"3\", config.Version{Number: 3})\n\t\t\tplugin3 := newMockPlugin(\"plugin3\", \"3\", config.Version{Number: 4})\n\n\t\t\tcli := CLI{\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(plugin1): plugin1,\n\t\t\t\t\tplugin.KeyFor(plugin2): plugin2,\n\t\t\t\t\tplugin.KeyFor(plugin3): plugin3,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tversions := cli.getAvailableProjectVersions()\n\n\t\t\tExpect(versions).To(ContainElement(\"\\\"3\\\"\"))\n\t\t\tExpect(versions).To(ContainElement(\"\\\"4\\\"\"))\n\t\t\tExpect(versions).To(HaveLen(2))\n\t\t})\n\n\t\tIt(\"should exclude deprecated plugins\", func() {\n\t\t\tdeprecatedPlugin := newMockDeprecatedPlugin(\"deprecated\", \"3\", \"use v4 instead\", config.Version{Number: 2})\n\t\t\tregularPlugin := newMockPlugin(\"regular\", \"3\", config.Version{Number: 3})\n\n\t\t\tcli := CLI{\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(deprecatedPlugin): deprecatedPlugin,\n\t\t\t\t\tplugin.KeyFor(regularPlugin):    regularPlugin,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tversions := cli.getAvailableProjectVersions()\n\n\t\t\tExpect(versions).NotTo(ContainElement(\"\\\"2\\\"\"))\n\t\t\tExpect(versions).To(ContainElement(\"\\\"3\\\"\"))\n\t\t})\n\n\t\tIt(\"should return sorted versions\", func() {\n\t\t\tplugin1 := newMockPlugin(\"plugin1\", \"3\", config.Version{Number: 4})\n\t\t\tplugin2 := newMockPlugin(\"plugin2\", \"3\", config.Version{Number: 2})\n\t\t\tplugin3 := newMockPlugin(\"plugin3\", \"3\", config.Version{Number: 3})\n\n\t\t\tcli := CLI{\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(plugin1): plugin1,\n\t\t\t\t\tplugin.KeyFor(plugin2): plugin2,\n\t\t\t\t\tplugin.KeyFor(plugin3): plugin3,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tversions := cli.getAvailableProjectVersions()\n\n\t\t\tExpect(versions).To(Equal([]string{\"\\\"2\\\"\", \"\\\"3\\\"\", \"\\\"4\\\"\"}))\n\t\t})\n\n\t\tIt(\"should return empty slice when no plugins\", func() {\n\t\t\tcli := CLI{\n\t\t\t\tplugins: map[string]plugin.Plugin{},\n\t\t\t}\n\n\t\t\tversions := cli.getAvailableProjectVersions()\n\t\t\tExpect(versions).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should handle plugin with multiple supported versions\", func() {\n\t\t\tmultiPlugin := newMockPlugin(\"multi\", \"3\",\n\t\t\t\tconfig.Version{Number: 2},\n\t\t\t\tconfig.Version{Number: 3},\n\t\t\t\tconfig.Version{Number: 4})\n\n\t\t\tcli := CLI{\n\t\t\t\tplugins: map[string]plugin.Plugin{\n\t\t\t\t\tplugin.KeyFor(multiPlugin): multiPlugin,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tversions := cli.getAvailableProjectVersions()\n\n\t\t\tExpect(versions).To(HaveLen(3))\n\t\t\tExpect(versions).To(ContainElement(\"\\\"2\\\"\"))\n\t\t\tExpect(versions).To(ContainElement(\"\\\"3\\\"\"))\n\t\t\tExpect(versions).To(ContainElement(\"\\\"4\\\"\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/options.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/external\"\n)\n\nvar retrievePluginsRoot = getPluginsRoot\n\n// Option is a function used as arguments to New in order to configure the resulting CLI.\ntype Option func(*CLI) error\n\n// WithCommandName is an Option that sets the CLI's root command name.\nfunc WithCommandName(name string) Option {\n\treturn func(c *CLI) error {\n\t\tc.commandName = name\n\t\treturn nil\n\t}\n}\n\n// WithVersion is an Option that defines the version string of the CLI.\nfunc WithVersion(version string) Option {\n\treturn func(c *CLI) error {\n\t\tc.version = version\n\t\treturn nil\n\t}\n}\n\n// WithCliVersion is an Option that defines only the version string of the CLI (no extra info).\nfunc WithCliVersion(version string) Option {\n\treturn func(c *CLI) error {\n\t\tc.cliVersion = version\n\t\treturn nil\n\t}\n}\n\n// WithDescription is an Option that sets the CLI's root description.\nfunc WithDescription(description string) Option {\n\treturn func(c *CLI) error {\n\t\tc.description = description\n\t\treturn nil\n\t}\n}\n\n// WithPlugins is an Option that sets the CLI's plugins.\n//\n// Specifying any invalid plugin results in an error.\nfunc WithPlugins(plugins ...plugin.Plugin) Option {\n\treturn func(c *CLI) error {\n\t\tfor _, p := range plugins {\n\t\t\tkey := plugin.KeyFor(p)\n\t\t\tif _, isConflicting := c.plugins[key]; isConflicting {\n\t\t\t\treturn fmt.Errorf(\"two plugins have the same key: %q\", key)\n\t\t\t}\n\t\t\tif err := plugin.Validate(p); err != nil {\n\t\t\t\treturn fmt.Errorf(\"broken pre-set plugin %q: %w\", key, err)\n\t\t\t}\n\t\t\tc.plugins[key] = p\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithDefaultPlugins is an Option that sets the CLI's default plugins.\n//\n// Specifying any invalid plugin results in an error.\nfunc WithDefaultPlugins(projectVersion config.Version, plugins ...plugin.Plugin) Option {\n\treturn func(c *CLI) error {\n\t\tif err := projectVersion.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"broken pre-set project version %q for default plugins: %w\", projectVersion, err)\n\t\t}\n\t\tif len(plugins) == 0 {\n\t\t\treturn fmt.Errorf(\"empty set of plugins provided for project version %q\", projectVersion)\n\t\t}\n\t\tfor _, p := range plugins {\n\t\t\tif err := plugin.Validate(p); err != nil {\n\t\t\t\treturn fmt.Errorf(\"broken pre-set default plugin %q: %w\", plugin.KeyFor(p), err)\n\t\t\t}\n\t\t\tif !plugin.SupportsVersion(p, projectVersion) {\n\t\t\t\treturn fmt.Errorf(\"default plugin %q doesn't support version %q\", plugin.KeyFor(p), projectVersion)\n\t\t\t}\n\t\t\tc.defaultPlugins[projectVersion] = append(c.defaultPlugins[projectVersion], plugin.KeyFor(p))\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithDefaultProjectVersion is an Option that sets the CLI's default project version.\n//\n// Setting an invalid version results in an error.\nfunc WithDefaultProjectVersion(version config.Version) Option {\n\treturn func(c *CLI) error {\n\t\tif err := version.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"broken pre-set default project version %q: %w\", version, err)\n\t\t}\n\t\tc.defaultProjectVersion = version\n\t\treturn nil\n\t}\n}\n\n// WithExtraCommands is an Option that adds extra subcommands to the CLI.\n//\n// Adding extra commands that duplicate existing commands results in an error.\nfunc WithExtraCommands(cmds ...*cobra.Command) Option {\n\treturn func(c *CLI) error {\n\t\t// We don't know the commands defined by the CLI yet so we are not checking if the extra commands\n\t\t// conflict with a pre-existing one yet. We do this after creating the base commands.\n\t\tc.extraCommands = append(c.extraCommands, cmds...)\n\t\treturn nil\n\t}\n}\n\n// WithExtraAlphaCommands is an Option that adds extra alpha subcommands to the CLI.\n//\n// Adding extra alpha commands that duplicate existing commands results in an error.\nfunc WithExtraAlphaCommands(cmds ...*cobra.Command) Option {\n\treturn func(c *CLI) error {\n\t\t// We don't know the commands defined by the CLI yet so we are not checking if the extra alpha commands\n\t\t// conflict with a pre-existing one yet. We do this after creating the base commands.\n\t\tc.extraAlphaCommands = append(c.extraAlphaCommands, cmds...)\n\t\treturn nil\n\t}\n}\n\n// WithCompletion is an Option that adds the completion subcommand.\nfunc WithCompletion() Option {\n\treturn func(c *CLI) error {\n\t\tc.completionCommand = true\n\t\treturn nil\n\t}\n}\n\n// WithFilesystem is an Option that allows to set the filesystem used in the CLI.\nfunc WithFilesystem(filesystem machinery.Filesystem) Option {\n\treturn func(c *CLI) error {\n\t\tif filesystem.FS == nil {\n\t\t\treturn errors.New(\"invalid filesystem\")\n\t\t}\n\n\t\tc.fs = filesystem\n\t\treturn nil\n\t}\n}\n\n// parseExternalPluginArgs returns the program arguments.\nfunc parseExternalPluginArgs() (args []string) {\n\t// Loop through os.Args and only get flags and their values that should be passed to the plugins\n\t// this also removes the --plugins flag and its values from the list passed to the external plugin\n\tfor i := range os.Args {\n\t\tif strings.Contains(os.Args[i], \"--\") && !strings.Contains(os.Args[i], \"--plugins\") {\n\t\t\targs = append(args, os.Args[i])\n\n\t\t\t// Don't go out of bounds and don't append the next value if it is a flag\n\t\t\tif i+1 < len(os.Args) && !strings.Contains(os.Args[i+1], \"--\") {\n\t\t\t\targs = append(args, os.Args[i+1])\n\t\t\t}\n\t\t}\n\t}\n\n\treturn args\n}\n\n// isHostSupported checks whether the host system is supported or not.\nfunc isHostSupported(host string) bool {\n\treturn slices.Contains(supportedPlatforms, host)\n}\n\n// getPluginsRoot gets the plugin root path.\nfunc getPluginsRoot(host string) (pluginsRoot string, err error) {\n\tif !isHostSupported(host) {\n\t\t// freebsd, openbsd, windows...\n\t\treturn \"\", fmt.Errorf(\"host not supported: %v\", host)\n\t}\n\n\t// if user provides specific path, return\n\tif pluginsPath := os.Getenv(\"EXTERNAL_PLUGINS_PATH\"); pluginsPath != \"\" {\n\t\t// verify if the path actually exists\n\t\tif _, err = os.Stat(pluginsPath); err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\t// the path does not exist\n\t\t\t\treturn \"\", fmt.Errorf(\"the specified path %s does not exist\", pluginsPath)\n\t\t\t}\n\t\t\t// some other error\n\t\t\treturn \"\", fmt.Errorf(\"error checking the path: %w\", err)\n\t\t}\n\t\t// the path exists\n\t\treturn pluginsPath, nil\n\t}\n\n\t// if no specific path, detects the host system and gets the plugins root based on the host.\n\tpluginsRelativePath := filepath.Join(\"kubebuilder\", \"plugins\")\n\tif xdgHome := os.Getenv(\"XDG_CONFIG_HOME\"); xdgHome != \"\" {\n\t\treturn filepath.Join(xdgHome, pluginsRelativePath), nil\n\t}\n\n\tswitch host {\n\tcase \"darwin\":\n\t\tslog.Debug(\"Detected host is macOS.\")\n\t\tpluginsRoot = filepath.Join(\"Library\", \"Application Support\", pluginsRelativePath)\n\tcase \"linux\":\n\t\tslog.Debug(\"Detected host is Linux.\")\n\t\tpluginsRoot = filepath.Join(\".config\", pluginsRelativePath)\n\t}\n\n\tuserHomeDir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error retrieving home dir: %w\", err)\n\t}\n\n\treturn filepath.Join(userHomeDir, pluginsRoot), nil\n}\n\n// DiscoverExternalPlugins discovers the external plugins in the plugins root directory\n// and adds them to external.Plugin.\nfunc DiscoverExternalPlugins(filesystem afero.Fs) (ps []plugin.Plugin, err error) {\n\tpluginsRoot, err := retrievePluginsRoot(runtime.GOOS)\n\tif err != nil {\n\t\tslog.Error(\"could not get plugins root\", \"error\", err)\n\t\treturn nil, fmt.Errorf(\"could not get plugins root: %w\", err)\n\t}\n\n\trootInfo, err := filesystem.Stat(pluginsRoot)\n\tif err != nil {\n\t\tif errors.Is(err, afero.ErrFileNotFound) {\n\t\t\tslog.Debug(\"External plugins dir does not exist, skipping external plugin parsing\", \"dir\", pluginsRoot)\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"error getting stats for plugins %s: %w\", pluginsRoot, err)\n\t}\n\tif !rootInfo.IsDir() {\n\t\tslog.Debug(\"External plugins path is not a directory, skipping external plugin parsing\", \"path\", pluginsRoot)\n\t\treturn nil, nil\n\t}\n\n\tpluginInfos, err := afero.ReadDir(filesystem, pluginsRoot)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading plugins directory %q: %w\", pluginsRoot, err)\n\t}\n\n\tfor _, pluginInfo := range pluginInfos {\n\t\tif !pluginInfo.IsDir() {\n\t\t\tslog.Debug(\"skipping parsing, not a directory\", \"name\", pluginInfo.Name())\n\t\t\tcontinue\n\t\t}\n\n\t\tversions, err := afero.ReadDir(filesystem, filepath.Join(pluginsRoot, pluginInfo.Name()))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading plugin directory %s: %w\",\n\t\t\t\tfilepath.Join(pluginsRoot, pluginInfo.Name()), err)\n\t\t}\n\n\t\tfor _, version := range versions {\n\t\t\tif !version.IsDir() {\n\t\t\t\tslog.Debug(\"skipping parsing, not a directory\", \"name\", version.Name())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpluginFiles, err := afero.ReadDir(filesystem, filepath.Join(pluginsRoot, pluginInfo.Name(), version.Name()))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error reading plugion version directory %q: %w\",\n\t\t\t\t\tfilepath.Join(pluginsRoot, pluginInfo.Name(), version.Name()), err)\n\t\t\t}\n\n\t\t\tfor _, pluginFile := range pluginFiles {\n\t\t\t\t// find the executable that matches the same name as info.Name().\n\t\t\t\t// if no match is found, compare the external plugin string name before dot\n\t\t\t\t// and match it with info.Name() which is the external plugin root dir.\n\t\t\t\t// for example: sample.sh --> sample, externalplugin.py --> externalplugin\n\t\t\t\ttrimmedPluginName := strings.Split(pluginFile.Name(), \".\")\n\t\t\t\tif trimmedPluginName[0] == \"\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid plugin name found %q\", pluginFile.Name())\n\t\t\t\t}\n\n\t\t\t\tif pluginFile.Name() == pluginInfo.Name() || trimmedPluginName[0] == pluginInfo.Name() {\n\t\t\t\t\t// check whether the external plugin is an executable.\n\t\t\t\t\tif !isPluginExecutable(pluginFile.Mode()) {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"external plugin %q found in path is not an executable\", pluginFile.Name())\n\t\t\t\t\t}\n\n\t\t\t\t\tep := external.Plugin{\n\t\t\t\t\t\tPName:                     pluginInfo.Name(),\n\t\t\t\t\t\tPath:                      filepath.Join(pluginsRoot, pluginInfo.Name(), version.Name(), pluginFile.Name()),\n\t\t\t\t\t\tPSupportedProjectVersions: []config.Version{cfgv3.Version},\n\t\t\t\t\t\tArgs:                      parseExternalPluginArgs(),\n\t\t\t\t\t}\n\n\t\t\t\t\tif err = ep.PVersion.Parse(version.Name()); err != nil {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"error parsing external plugin version %q: %w\", version.Name(), err)\n\t\t\t\t\t}\n\n\t\t\t\t\tslog.Info(\"Adding external plugin\", \"plugin name\", ep.Name())\n\n\t\t\t\t\tps = append(ps, ep)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ps, nil\n}\n\n// isPluginExecutable checks if a plugin is an executable based on the bitmask and returns true or false.\nfunc isPluginExecutable(mode fs.FileMode) bool {\n\treturn mode&0o111 != 0\n}\n"
  },
  {
    "path": "pkg/cli/options_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar _ = Describe(\"Discover external plugins\", func() {\n\tContext(\"with valid plugins root path\", func() {\n\t\tvar (\n\t\t\thomePath   string\n\t\t\tcustomPath string\n\t\t\t// store user's original EXTERNAL_PLUGINS_PATH\n\t\t\toriginalPluginPath string\n\t\t\txdghome            string\n\t\t\t// store user's original XDG_CONFIG_HOME\n\t\t\toriginalXdghome string\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\thomePath = os.Getenv(\"HOME\")\n\t\t\tcustomPath = \"/tmp/myplugins\"\n\t\t})\n\n\t\tWhen(\"XDG_CONFIG_HOME is not set and using the $HOME environment variable\", func() {\n\t\t\t// store and unset the XDG_CONFIG_HOME\n\t\t\tBeforeEach(func() {\n\t\t\t\toriginalXdghome = os.Getenv(\"XDG_CONFIG_HOME\")\n\t\t\t\terr := os.Unsetenv(\"XDG_CONFIG_HOME\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif originalXdghome != \"\" {\n\t\t\t\t\t// restore the original value\n\t\t\t\t\terr := os.Setenv(\"XDG_CONFIG_HOME\", originalXdghome)\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should return the correct path for the darwin OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"darwin\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(plgPath).To(Equal(fmt.Sprintf(\"%s/Library/Application Support/kubebuilder/plugins\", homePath)))\n\t\t\t})\n\n\t\t\tIt(\"should return the correct path for the linux OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"linux\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(plgPath).To(Equal(fmt.Sprintf(\"%s/.config/kubebuilder/plugins\", homePath)))\n\t\t\t})\n\n\t\t\tIt(\"should return error when the host is not darwin / linux\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"random\")\n\t\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"host not supported\"))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"XDG_CONFIG_HOME is set\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\t// store and set the XDG_CONFIG_HOME\n\t\t\t\toriginalXdghome = os.Getenv(\"XDG_CONFIG_HOME\")\n\t\t\t\terr := os.Setenv(\"XDG_CONFIG_HOME\", fmt.Sprintf(\"%s/.config\", homePath))\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\txdghome = os.Getenv(\"XDG_CONFIG_HOME\")\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif originalXdghome != \"\" {\n\t\t\t\t\t// restore the original value\n\t\t\t\t\terr := os.Setenv(\"XDG_CONFIG_HOME\", originalXdghome)\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t} else {\n\t\t\t\t\t// unset if it was originally unset\n\t\t\t\t\terr := os.Unsetenv(\"XDG_CONFIG_HOME\")\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should return the correct path for the darwin OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"darwin\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(plgPath).To(Equal(fmt.Sprintf(\"%s/kubebuilder/plugins\", xdghome)))\n\t\t\t})\n\n\t\t\tIt(\"should return the correct path for the linux OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"linux\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(plgPath).To(Equal(fmt.Sprintf(\"%s/kubebuilder/plugins\", xdghome)))\n\t\t\t})\n\n\t\t\tIt(\"should return error when the host is not darwin / linux\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"random\")\n\t\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"host not supported\"))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"using the custom path\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\terr := os.MkdirAll(customPath, 0o750)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\t// store and set the EXTERNAL_PLUGINS_PATH\n\t\t\t\toriginalPluginPath = os.Getenv(\"EXTERNAL_PLUGINS_PATH\")\n\t\t\t\terr = os.Setenv(\"EXTERNAL_PLUGINS_PATH\", customPath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tAfterEach(func() {\n\t\t\t\tif originalPluginPath != \"\" {\n\t\t\t\t\t// restore the original value\n\t\t\t\t\terr := os.Setenv(\"EXTERNAL_PLUGINS_PATH\", originalPluginPath)\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t} else {\n\t\t\t\t\t// unset if it was originally unset\n\t\t\t\t\terr := os.Unsetenv(\"EXTERNAL_PLUGINS_PATH\")\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should return the user given path for darwin OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"darwin\")\n\t\t\t\tExpect(plgPath).To(Equal(customPath))\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should return the user given path for linux OS\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"linux\")\n\t\t\t\tExpect(plgPath).To(Equal(customPath))\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should report error when the host is not darwin / linux\", func() {\n\t\t\t\tplgPath, err := getPluginsRoot(\"random\")\n\t\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"host not supported\"))\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"with invalid plugins root path\", func() {\n\t\tvar originalPluginPath string\n\n\t\tBeforeEach(func() {\n\t\t\toriginalPluginPath = os.Getenv(\"EXTERNAL_PLUGINS_PATH\")\n\t\t\terr := os.Setenv(\"EXTERNAL_PLUGINS_PATH\", \"/non/existent/path\")\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tif originalPluginPath != \"\" {\n\t\t\t\t// restore the original value\n\t\t\t\terr := os.Setenv(\"EXTERNAL_PLUGINS_PATH\", originalPluginPath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t} else {\n\t\t\t\t// unset if it was originally unset\n\t\t\t\terr := os.Unsetenv(\"EXTERNAL_PLUGINS_PATH\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return an error for the darwin OS\", func() {\n\t\t\tplgPath, err := getPluginsRoot(\"darwin\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t})\n\n\t\tIt(\"should return an error for the linux OS\", func() {\n\t\t\tplgPath, err := getPluginsRoot(\"linux\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t})\n\n\t\tIt(\"should return an error when the host is not darwin / linux\", func() {\n\t\t\tplgPath, err := getPluginsRoot(\"random\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(plgPath).To(Equal(\"\"))\n\t\t})\n\t})\n\n\tContext(\"when plugin executables exist in the expected plugin directories\", func() {\n\t\tconst (\n\t\t\tfilePermissions  os.FileMode = 755\n\t\t\ttestPluginScript             = `#!/bin/bash\n\t\t\techo \"This is an external plugin\"\n\t\t\t`\n\t\t)\n\n\t\tvar (\n\t\t\tpluginFilePath string\n\t\t\tpluginFileName string\n\t\t\tpluginPath     string\n\t\t\tpluginsRoot    string\n\t\t\tplugins        []plugin.Plugin\n\t\t\tf              afero.File\n\t\t\tfilesystem     machinery.Filesystem\n\t\t\terr            error\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tfilesystem = machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tpluginPath, err = getPluginsRoot(runtime.GOOS)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tpluginFileName = \"externalPlugin.sh\"\n\t\t\tpluginFilePath = filepath.Join(pluginPath, \"externalPlugin\", \"v1\", pluginFileName)\n\n\t\t\terr = filesystem.FS.MkdirAll(filepath.Dir(pluginFilePath), 0o700)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tf, err = filesystem.FS.Create(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t_, err = filesystem.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should discover the external plugin executable without any errors\", func() {\n\t\t\t// test that DiscoverExternalPlugins works if the plugin file is an executable and\n\t\t\t// is found in the expected path\n\t\t\t_, err = f.WriteString(testPluginScript)\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\terr = filesystem.FS.Chmod(pluginFilePath, filePermissions)\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t_, err = filesystem.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tplugins, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(plugins).NotTo(BeNil())\n\t\t\tExpect(plugins).To(HaveLen(1))\n\t\t\tExpect(plugins[0].Name()).To(Equal(\"externalPlugin\"))\n\t\t\tExpect(plugins[0].Version().Number).To(Equal(1))\n\t\t})\n\n\t\tIt(\"should discover multiple external plugins and return the plugins without any errors\", func() {\n\t\t\t// set the execute permissions on the first plugin executable\n\t\t\terr = filesystem.FS.Chmod(pluginFilePath, filePermissions)\n\n\t\t\tpluginFileName = \"myotherexternalPlugin.sh\"\n\t\t\tpluginFilePath = filepath.Join(pluginPath, \"myotherexternalPlugin\", \"v1\", pluginFileName)\n\n\t\t\tf, err = filesystem.FS.Create(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t_, err = filesystem.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t_, err = f.WriteString(testPluginScript)\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t// set the execute permissions on the second plugin executable\n\t\t\terr = filesystem.FS.Chmod(pluginFilePath, filePermissions)\n\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t_, err = filesystem.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tplugins, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(plugins).NotTo(BeNil())\n\t\t\tExpect(plugins).To(HaveLen(2))\n\n\t\t\tExpect(plugins[0].Name()).To(Equal(\"externalPlugin\"))\n\t\t\tExpect(plugins[1].Name()).To(Equal(\"myotherexternalPlugin\"))\n\t\t})\n\n\t\tContext(\"that are invalid\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tfilesystem = machinery.Filesystem{\n\t\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t\t}\n\n\t\t\t\tpluginPath, err = getPluginsRoot(runtime.GOOS)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should error if the plugin found is not an executable\", func() {\n\t\t\t\tpluginFileName = \"externalPlugin.sh\"\n\t\t\t\tpluginFilePath = filepath.Join(pluginPath, \"externalPlugin\", \"v1\", pluginFileName)\n\n\t\t\t\terr = filesystem.FS.MkdirAll(filepath.Dir(pluginFilePath), 0o700)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tvar file fs.File\n\t\t\t\tfile, err = filesystem.FS.Create(pluginFilePath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(file).ToNot(BeNil())\n\n\t\t\t\t_, err = filesystem.FS.Stat(pluginFilePath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\t// set the plugin file permissions to read-only\n\t\t\t\terr = filesystem.FS.Chmod(pluginFilePath, 0o444)\n\t\t\t\tExpect(err).To(Not(HaveOccurred()))\n\n\t\t\t\tplugins, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"not an executable\"))\n\t\t\t\tExpect(plugins).To(BeEmpty())\n\t\t\t})\n\n\t\t\tIt(\"should error if the plugin found has an invalid plugin name\", func() {\n\t\t\t\tpluginFileName = \".sh\"\n\t\t\t\tpluginFilePath = filepath.Join(pluginPath, \"externalPlugin\", \"v1\", pluginFileName)\n\n\t\t\t\terr = filesystem.FS.MkdirAll(filepath.Dir(pluginFilePath), 0o700)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tf, err = filesystem.FS.Create(pluginFilePath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t\tplugins, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"invalid plugin name found\"))\n\t\t\t\tExpect(plugins).To(BeEmpty())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"that does not match the plugin root directory name\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tfilesystem = machinery.Filesystem{\n\t\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t\t}\n\n\t\t\t\tpluginPath, err = getPluginsRoot(runtime.GOOS)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should skip adding the external plugin and not return any errors\", func() {\n\t\t\t\tpluginFileName = \"random.sh\"\n\t\t\t\tpluginFilePath = filepath.Join(pluginPath, \"externalPlugin\", \"v1\", pluginFileName)\n\n\t\t\t\terr = filesystem.FS.MkdirAll(filepath.Dir(pluginFilePath), 0o700)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tf, err = filesystem.FS.Create(pluginFilePath)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t\terr = filesystem.FS.Chmod(pluginFilePath, filePermissions)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tvar ps []plugin.Plugin\n\t\t\t\tps, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(ps).To(BeEmpty())\n\t\t\t})\n\n\t\t\tIt(\"should fail if pluginsroot is empty\", func() {\n\t\t\t\terrPluginsRoot := errors.New(\"could not retrieve plugins root\")\n\t\t\t\tretrievePluginsRoot = func(_ string) (string, error) {\n\t\t\t\t\treturn \"\", errPluginsRoot\n\t\t\t\t}\n\n\t\t\t\t_, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\n\t\t\t\tExpect(errors.Unwrap(err)).To(MatchError(errPluginsRoot))\n\t\t\t})\n\n\t\t\tIt(\"should skip parsing of directories if plugins root is not a directory\", func() {\n\t\t\t\tretrievePluginsRoot = func(_ string) (string, error) {\n\t\t\t\t\treturn \"externalplugin.sh\", nil\n\t\t\t\t}\n\n\t\t\t\t_, err = DiscoverExternalPlugins(filesystem.FS)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should return full path to the external plugins without XDG_CONFIG_HOME\", func() {\n\t\t\t\tif _, ok := os.LookupEnv(\"XDG_CONFIG_HOME\"); ok {\n\t\t\t\t\terr = os.Setenv(\"XDG_CONFIG_HOME\", \"\")\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\n\t\t\t\thome := os.Getenv(\"HOME\")\n\n\t\t\t\tpluginsRoot, err = getPluginsRoot(\"darwin\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\texpected := filepath.Join(home, \"Library\", \"Application Support\", \"kubebuilder\", \"plugins\")\n\t\t\t\tExpect(pluginsRoot).To(Equal(expected))\n\n\t\t\t\tpluginsRoot, err = getPluginsRoot(\"linux\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\texpected = filepath.Join(home, \".config\", \"kubebuilder\", \"plugins\")\n\t\t\t\tExpect(pluginsRoot).To(Equal(expected))\n\t\t\t})\n\n\t\t\tIt(\"should return full path to the external plugins with XDG_CONFIG_HOME\", func() {\n\t\t\t\terr = os.Setenv(\"XDG_CONFIG_HOME\", \"/some/random/path\")\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tpluginsRoot, err = getPluginsRoot(runtime.GOOS)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\tExpect(pluginsRoot).To(Equal(\"/some/random/path/kubebuilder/plugins\"))\n\t\t\t})\n\n\t\t\tIt(\"should return error when home directory is set to empty\", func() {\n\t\t\t\t_, ok := os.LookupEnv(\"XDG_CONFIG_HOME\")\n\t\t\t\tif ok {\n\t\t\t\t\terr = os.Setenv(\"XDG_CONFIG_HOME\", \"\")\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\n\t\t\t\t_, ok = os.LookupEnv(\"HOME\")\n\t\t\t\tif ok {\n\t\t\t\t\terr = os.Setenv(\"HOME\", \"\")\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t}\n\n\t\t\t\tpluginsRoot, err = getPluginsRoot(runtime.GOOS)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(pluginsRoot).To(Equal(\"\"))\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"error retrieving home dir\"))\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"parsing flags for external plugins\", func() {\n\t\tIt(\"should only parse flags excluding the `--plugins` flag\", func() {\n\t\t\t// change the os.Args for this test and set them back after\n\t\t\toldArgs := os.Args\n\t\t\tdefer func() { os.Args = oldArgs }()\n\t\t\tos.Args = []string{\n\t\t\t\t\"kubebuilder\",\n\t\t\t\t\"init\",\n\t\t\t\t\"--plugins\",\n\t\t\t\t\"myexternalplugin/v1\",\n\t\t\t\t\"--domain\",\n\t\t\t\t\"example.com\",\n\t\t\t\t\"--binary-flag\",\n\t\t\t\t\"--license\",\n\t\t\t\t\"apache2\",\n\t\t\t\t\"--another-binary\",\n\t\t\t}\n\n\t\t\targs := parseExternalPluginArgs()\n\t\t\tExpect(args).Should(ContainElements(\n\t\t\t\t\"--domain\",\n\t\t\t\t\"example.com\",\n\t\t\t\t\"--binary-flag\",\n\t\t\t\t\"--license\",\n\t\t\t\t\"apache2\",\n\t\t\t\t\"--another-binary\",\n\t\t\t))\n\n\t\t\tExpect(args).ShouldNot(ContainElements(\n\t\t\t\t\"kubebuilder\",\n\t\t\t\t\"init\",\n\t\t\t\t\"--plugins\",\n\t\t\t\t\"myexternalplugin/v1\",\n\t\t\t))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"CLI options\", func() {\n\tconst (\n\t\tpluginName    = \"plugin\"\n\t\tpluginVersion = \"v1\"\n\t)\n\n\tvar (\n\t\tc   *CLI\n\t\terr error\n\n\t\tprojectVersion config.Version\n\n\t\tp   plugin.Plugin\n\t\tnp1 plugin.Plugin\n\t\tnp2 mockPlugin\n\t\tnp3 plugin.Plugin\n\t\tnp4 plugin.Plugin\n\t)\n\n\tBeforeEach(func() {\n\t\tprojectVersion = config.Version{Number: 1}\n\n\t\tp = newMockPlugin(pluginName, pluginVersion, projectVersion)\n\t\tnp1 = newMockPlugin(\"Plugin\", pluginVersion, projectVersion)\n\t\tnp2 = mockPlugin{pluginName, plugin.Version{Number: -1}, []config.Version{projectVersion}}\n\t\tnp3 = newMockPlugin(pluginName, pluginVersion)\n\t\tnp4 = newMockPlugin(pluginName, pluginVersion, config.Version{})\n\t})\n\n\tContext(\"WithCommandName\", func() {\n\t\tIt(\"should use provided command name\", func() {\n\t\t\tcommandName := \"other-command\"\n\t\t\tc, err = newCLI(WithCommandName(commandName))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.commandName).To(Equal(commandName))\n\t\t})\n\t})\n\n\tContext(\"WithVersion\", func() {\n\t\tIt(\"should use the provided version string\", func() {\n\t\t\tversion := \"Version: 0.0.0\"\n\t\t\tc, err = newCLI(WithVersion(version))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.version).To(Equal(version))\n\t\t})\n\t})\n\n\tContext(\"WithCliVersion\", func() {\n\t\tIt(\"should use the provided CLI version string\", func() {\n\t\t\tcliVersion := \"v4.0.0\"\n\t\t\tc, err = newCLI(WithCliVersion(cliVersion))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.cliVersion).To(Equal(cliVersion))\n\t\t})\n\t})\n\n\tContext(\"WithDescription\", func() {\n\t\tIt(\"should use the provided description string\", func() {\n\t\t\tdescription := \"alternative description\"\n\t\t\tc, err = newCLI(WithDescription(description))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.description).To(Equal(description))\n\t\t})\n\t})\n\n\tContext(\"WithPlugins\", func() {\n\t\tIt(\"should return a valid CLI\", func() {\n\t\t\tc, err = newCLI(WithPlugins(p))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.plugins).To(Equal(map[string]plugin.Plugin{plugin.KeyFor(p): p}))\n\t\t})\n\n\t\tWhen(\"providing plugins with same keys\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(p, p))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing plugins with same keys in two steps\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(p), WithPlugins(p))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid name\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(np1))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid version\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(np2))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an empty list of supported versions\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(np3))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid list of supported versions\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithPlugins(np4))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"WithDefaultPlugins\", func() {\n\t\tIt(\"should return a valid CLI\", func() {\n\t\t\tc, err = newCLI(WithDefaultPlugins(projectVersion, p))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.defaultPlugins).To(Equal(map[config.Version][]string{projectVersion: {plugin.KeyFor(p)}}))\n\t\t})\n\n\t\tWhen(\"providing an invalid project version\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(config.Version{}, p))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing an empty set of plugins\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(projectVersion))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid name\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(projectVersion, np1))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid version\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(projectVersion, np2))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an empty list of supported versions\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(projectVersion, np3))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a plugin with an invalid list of supported versions\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(projectVersion, np4))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a default plugin for an unsupported project version\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\t_, err = newCLI(WithDefaultPlugins(config.Version{Number: 2}, p))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"WithDefaultProjectVersion\", func() {\n\t\tDescribeTable(\"should return a valid CLI\",\n\t\t\tfunc(projectVersion config.Version) {\n\t\t\t\tc, err = newCLI(WithDefaultProjectVersion(projectVersion))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(c).NotTo(BeNil())\n\t\t\t\tExpect(c.defaultProjectVersion).To(Equal(projectVersion))\n\t\t\t},\n\t\t\tEntry(\"for version `2`\", config.Version{Number: 2}),\n\t\t\tEntry(\"for version `3-alpha`\", config.Version{Number: 3, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version `3`\", config.Version{Number: 3}),\n\t\t)\n\n\t\tDescribeTable(\"should fail\",\n\t\t\tfunc(projectVersion config.Version) {\n\t\t\t\t_, err = newCLI(WithDefaultProjectVersion(projectVersion))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t},\n\t\t\tEntry(\"for empty version\", config.Version{}),\n\t\t\tEntry(\"for invalid stage\", config.Version{Number: 1, Stage: stage.Stage(27)}),\n\t\t)\n\t})\n\n\tContext(\"WithExtraCommands\", func() {\n\t\tIt(\"should return a valid CLI with extra commands\", func() {\n\t\t\tcommandTest := &cobra.Command{\n\t\t\t\tUse: \"example\",\n\t\t\t}\n\t\t\tc, err = newCLI(WithExtraCommands(commandTest))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.extraCommands).NotTo(BeNil())\n\t\t\tExpect(c.extraCommands).To(HaveLen(1))\n\t\t\tExpect(c.extraCommands[0]).NotTo(BeNil())\n\t\t\tExpect(c.extraCommands[0].Use).To(Equal(commandTest.Use))\n\t\t})\n\t})\n\n\tContext(\"WithExtraAlphaCommands\", func() {\n\t\tIt(\"should return a valid CLI with extra alpha commands\", func() {\n\t\t\tcommandTest := &cobra.Command{\n\t\t\t\tUse: \"example\",\n\t\t\t}\n\t\t\tc, err = newCLI(WithExtraAlphaCommands(commandTest))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.extraAlphaCommands).NotTo(BeNil())\n\t\t\tExpect(c.extraAlphaCommands).To(HaveLen(1))\n\t\t\tExpect(c.extraAlphaCommands[0]).NotTo(BeNil())\n\t\t\tExpect(c.extraAlphaCommands[0].Use).To(Equal(commandTest.Use))\n\t\t})\n\t})\n\n\tContext(\"WithCompletion\", func() {\n\t\tIt(\"should not add the completion command by default\", func() {\n\t\t\tc, err = newCLI()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.completionCommand).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should add the completion command if requested\", func() {\n\t\t\tc, err = newCLI(WithCompletion())\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(c).NotTo(BeNil())\n\t\t\tExpect(c.completionCommand).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"WithFilesystem\", func() {\n\t\tWhen(\"providing a valid filesystem\", func() {\n\t\t\tIt(\"should use the provided filesystem\", func() {\n\t\t\t\tfs := machinery.Filesystem{\n\t\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t\t}\n\t\t\t\tc, err = newCLI(WithFilesystem(fs))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(c).NotTo(BeNil())\n\t\t\t\tExpect(c.fs).To(Equal(fs))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"providing a invalid filesystem\", func() {\n\t\t\tIt(\"should return an error\", func() {\n\t\t\t\tfs := machinery.Filesystem{}\n\t\t\t\tc, err = newCLI(WithFilesystem(fs))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(c).To(BeNil())\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/resource.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nconst (\n\tgroupPresent   = \"group flag present but empty\"\n\tversionPresent = \"version flag present but empty\"\n\tkindPresent    = \"kind flag present but empty\"\n)\n\n// resourceOptions contains the information required to build a new resource.Resource.\ntype resourceOptions struct {\n\tresource.GVK\n}\n\nfunc bindResourceFlags(fs *pflag.FlagSet) *resourceOptions {\n\toptions := &resourceOptions{}\n\n\tfs.StringVar(&options.Group, \"group\", \"\", \"resource Group\")\n\tfs.StringVar(&options.Version, \"version\", \"\", \"resource Version\")\n\tfs.StringVar(&options.Kind, \"kind\", \"\", \"resource Kind\")\n\n\treturn options\n}\n\n// validate verifies that all the fields have valid values.\nfunc (opts resourceOptions) validate() error {\n\t// Check that the required flags did not get a flag as their value.\n\t// We can safely look for a '-' as the first char as none of the fields accepts it.\n\t// NOTE: We must do this for all the required flags first or we may output the wrong\n\t// error as flags may seem to be missing because Cobra assigned them to another flag.\n\tif strings.HasPrefix(opts.Group, \"-\") {\n\t\treturn errors.New(groupPresent)\n\t}\n\tif strings.HasPrefix(opts.Version, \"-\") {\n\t\treturn errors.New(versionPresent)\n\t}\n\tif strings.HasPrefix(opts.Kind, \"-\") {\n\t\treturn errors.New(kindPresent)\n\t}\n\n\t// We do not check here if the GVK values are empty because that would\n\t// make them mandatory and some plugins may want to set default values.\n\t// Instead, this is checked by resource.GVK.Validate()\n\n\treturn nil\n}\n\n// newResource creates a new resource from the options\nfunc (opts resourceOptions) newResource() *resource.Resource {\n\treturn &resource.Resource{\n\t\tGVK: resource.GVK{ // Remove whitespaces to prevent values like \" \" pass validation\n\t\t\tGroup:   strings.TrimSpace(opts.Group),\n\t\t\tDomain:  strings.TrimSpace(opts.Domain),\n\t\t\tVersion: strings.TrimSpace(opts.Version),\n\t\t\tKind:    strings.TrimSpace(opts.Kind),\n\t\t},\n\t\tPlural:   resource.RegularPlural(opts.Kind),\n\t\tAPI:      &resource.API{},\n\t\tWebhooks: &resource.Webhooks{},\n\t}\n}\n"
  },
  {
    "path": "pkg/cli/resource_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"resourceOptions\", func() {\n\tconst (\n\t\tgroup   = \"crew\"\n\t\tdomain  = \"test.io\"\n\t\tversion = \"v1\"\n\t\tkind    = \"FirstMate\"\n\t)\n\n\tvar (\n\t\tfullGVK     resource.GVK\n\t\tnoDomainGVK resource.GVK\n\t\tnoGroupGVK  resource.GVK\n\t)\n\n\tBeforeEach(func() {\n\t\tfullGVK = resource.GVK{\n\t\t\tGroup:   group,\n\t\t\tDomain:  domain,\n\t\t\tVersion: version,\n\t\t\tKind:    kind,\n\t\t}\n\t\tnoDomainGVK = resource.GVK{\n\t\t\tGroup:   group,\n\t\t\tVersion: version,\n\t\t\tKind:    kind,\n\t\t}\n\t\tnoGroupGVK = resource.GVK{\n\t\t\tDomain:  domain,\n\t\t\tVersion: version,\n\t\t\tKind:    kind,\n\t\t}\n\t})\n\n\tContext(\"validate\", func() {\n\t\tDescribeTable(\"should succeed for valid options\",\n\t\t\tfunc(options resourceOptions) { Expect(options.validate()).To(Succeed()) },\n\t\t\tEntry(\"full GVK\", resourceOptions{GVK: fullGVK}),\n\t\t\tEntry(\"missing domain\", resourceOptions{GVK: noDomainGVK}),\n\t\t\tEntry(\"missing group\", resourceOptions{GVK: noGroupGVK}),\n\t\t)\n\n\t\tDescribeTable(\"should fail for invalid options\",\n\t\t\tfunc(options resourceOptions) { Expect(options.validate()).NotTo(Succeed()) },\n\t\t\tEntry(\"group flag captured another flag\", resourceOptions{GVK: resource.GVK{Group: \"--version\"}}),\n\t\t\tEntry(\"version flag captured another flag\", resourceOptions{GVK: resource.GVK{Version: \"--kind\"}}),\n\t\t\tEntry(\"kind flag captured another flag\", resourceOptions{GVK: resource.GVK{Kind: \"--group\"}}),\n\t\t)\n\t})\n\n\tContext(\"newResource\", func() {\n\t\tDescribeTable(\"should succeed if the Resource is valid\",\n\t\t\tfunc(getOpts func() resourceOptions) {\n\t\t\t\toptions := getOpts()\n\n\t\t\t\tExpect(options.validate()).To(Succeed())\n\n\t\t\t\tresource := options.newResource()\n\t\t\t\tExpect(resource.Validate()).To(Succeed())\n\t\t\t\tExpect(resource.GVK.IsEqualTo(options.GVK)).To(BeTrue())\n\t\t\t\tExpect(resource.Path).To(Equal(\"\"))\n\t\t\t\tExpect(resource.API).NotTo(BeNil())\n\t\t\t\tExpect(resource.API.CRDVersion).To(Equal(\"\"))\n\t\t\t\tExpect(resource.API.Namespaced).To(BeFalse())\n\t\t\t\tExpect(resource.Controller).To(BeFalse())\n\t\t\t\tExpect(resource.Webhooks).NotTo(BeNil())\n\t\t\t\tExpect(resource.Webhooks.WebhookVersion).To(Equal(\"\"))\n\t\t\t\tExpect(resource.Webhooks.Defaulting).To(BeFalse())\n\t\t\t\tExpect(resource.Webhooks.Validation).To(BeFalse())\n\t\t\t\tExpect(resource.Webhooks.Conversion).To(BeFalse())\n\t\t\t},\n\t\t\tEntry(\"full GVK\", func() resourceOptions { return resourceOptions{GVK: fullGVK} }),\n\t\t\tEntry(\"missing domain\", func() resourceOptions { return resourceOptions{GVK: noDomainGVK} }),\n\t\t\tEntry(\"missing group\", func() resourceOptions { return resourceOptions{GVK: noGroupGVK} }),\n\t\t)\n\n\t\tDescribeTable(\"should default the Plural by pluralizing the Kind\",\n\t\t\tfunc(kind, plural string) {\n\t\t\t\toptions := resourceOptions{GVK: resource.GVK{Group: group, Version: version, Kind: kind}}\n\t\t\t\tExpect(options.validate()).To(Succeed())\n\n\t\t\t\tresource := options.newResource()\n\t\t\t\tExpect(resource.Validate()).To(Succeed())\n\t\t\t\tExpect(resource.GVK.IsEqualTo(options.GVK)).To(BeTrue())\n\t\t\t\tExpect(resource.Plural).To(Equal(plural))\n\t\t\t},\n\t\t\tEntry(\"for `FirstMate`\", \"FirstMate\", \"firstmates\"),\n\t\t\tEntry(\"for `Fish`\", \"Fish\", \"fish\"),\n\t\t\tEntry(\"for `Helmswoman`\", \"Helmswoman\", \"helmswomen\"),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/root.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar (\n\tsupportedPlatforms = []string{\"darwin\", \"linux\"}\n\t// errHelpDisplayed is returned when help is displayed to prevent command execution\n\terrHelpDisplayed = errors.New(\"help displayed\")\n)\n\n// isHelpFlag checks if the given string is a help flag\nfunc isHelpFlag(s string) bool {\n\treturn s == \"--help\" || s == \"-h\" || s == \"help\"\n}\n\n// getShortKey converts a full plugin key to a short display key\n// Example: \"deploy-image.go.kubebuilder.io/v1-alpha\" -> \"deploy-image/v1-alpha\"\nfunc getShortKey(fullKey string) string {\n\tname, version := plugin.SplitKey(fullKey)\n\n\t// Extract the short name (part before .kubebuilder.io or other domain)\n\tshortName := name\n\tif strings.Contains(name, \".kubebuilder.io\") {\n\t\tshortName = strings.TrimSuffix(name, \".kubebuilder.io\")\n\t} else if idx := strings.LastIndex(name, \".\"); idx > 0 {\n\t\t// For external plugins, try to get a reasonable short name\n\t\t// Keep the part before the last dot if it looks like a domain\n\t\tparts := strings.Split(name, \".\")\n\t\tif len(parts) > 2 {\n\t\t\tshortName = strings.Join(parts[:len(parts)-1], \".\")\n\t\t}\n\t}\n\n\t// Strip common suffixes for cleaner display\n\t// e.g., \"deploy-image.go\" -> \"deploy-image\", \"kustomize.common\" -> \"kustomize\"\n\tshortName = strings.TrimSuffix(shortName, \".go\")\n\tshortName = strings.TrimSuffix(shortName, \".common\")\n\n\tif version == \"\" {\n\t\treturn shortName\n\t}\n\treturn shortName + \"/\" + version\n}\n\n// getPluginDescription returns a short description for a plugin key\n// This is a fallback for plugins that don't implement Describable interface\nfunc getPluginDescription(_ string) string {\n\t// Fallback for external plugins that don't provide descriptions\n\treturn \"External or custom plugin\"\n}\n\nfunc (c CLI) newRootCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:     c.commandName,\n\t\tLong:    c.description,\n\t\tExample: c.rootExamples(),\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\treturn cmd.Help()\n\t\t},\n\t\tPersistentPreRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\t// Check if --plugins flag contains help flags (--help, -h, help)\n\t\t\t// This handles cases like: kubebuilder init --plugins --help\n\t\t\tif pluginKeys, err := cmd.Flags().GetStringSlice(pluginsFlag); err == nil {\n\t\t\t\tfor _, key := range pluginKeys {\n\t\t\t\t\tkey = strings.TrimSpace(key)\n\t\t\t\t\tif isHelpFlag(key) {\n\t\t\t\t\t\t// Help was requested, show help and stop execution\n\t\t\t\t\t\tcmd.SilenceUsage = true\n\t\t\t\t\t\tcmd.SilenceErrors = true\n\t\t\t\t\t\t_ = cmd.Help()\n\t\t\t\t\t\treturn errHelpDisplayed\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\t// Global flags for all subcommands.\n\tcmd.PersistentFlags().StringSlice(pluginsFlag, nil, \"plugin keys to be used for this subcommand execution\")\n\n\t// Register --project-version on the root command so that it shows up in help.\n\tcmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), \"project version\")\n\n\t// As the root command will be used to shot the help message under some error conditions,\n\t// like during plugin resolving, we need to allow unknown flags to prevent parsing errors.\n\tcmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true}\n\n\treturn cmd\n}\n\n// rootExamples builds the examples string for the root command before resolving plugins\nfunc (c CLI) rootExamples() string {\n\tstr := fmt.Sprintf(`Get started by initializing a new project:\n\n    %[1]s init --domain <YOUR_DOMAIN>\n\nThe default plugin scaffold includes everything you need. To use optional plugins:\n\n    %[1]s init --plugins=<PLUGIN_KEYS>\n\nAvailable plugins:\n\n%[2]s\n\nTo see which plugins support a specific command:\n\n    %[1]s <init|edit|create> --help\n`,\n\t\tc.commandName, c.getPluginTable())\n\n\tif len(c.defaultPlugins) != 0 {\n\t\tif defaultPlugins, found := c.defaultPlugins[c.defaultProjectVersion]; found {\n\t\t\tstr += fmt.Sprintf(\"\\nDefault plugin: %q\\n\", strings.Join(defaultPlugins, \",\"))\n\t\t}\n\t}\n\n\treturn str\n}\n\n// getPluginTable returns an ASCII table of the available plugins and their supported project versions.\nfunc (c CLI) getPluginTable() string {\n\treturn c.getPluginTableFiltered(nil)\n}\n\n// getPluginTableFilteredForSubcommand returns a filtered list of plugins for subcommands,\n// excluding the default scaffold bundle and its component plugins.\nfunc (c CLI) getPluginTableFilteredForSubcommand(filter func(plugin.Plugin) bool) string {\n\treturn c.getPluginTableFilteredWithOptions(filter, true)\n}\n\n// getPluginTableFiltered returns a formatted list of plugins filtered by a predicate.\n// If filter is nil, all plugins are included.\n// Deprecated plugins are automatically excluded from help output.\nfunc (c CLI) getPluginTableFiltered(filter func(plugin.Plugin) bool) string {\n\treturn c.getPluginTableFilteredWithOptions(filter, false)\n}\n\n// getPluginTableFilteredWithOptions returns a formatted list of plugins with filtering options.\nfunc (c CLI) getPluginTableFilteredWithOptions(filter func(plugin.Plugin) bool, excludeDefaultScaffold bool) string {\n\ttype pluginInfo struct {\n\t\tshortKey    string\n\t\tfullKey     string\n\t\tdescription string\n\t\tversions    string\n\t}\n\n\tplugins := make([]pluginInfo, 0, len(c.plugins))\n\n\tfor pluginKey, p := range c.plugins {\n\t\t// Skip deprecated plugins in help output\n\t\tif deprecated, ok := p.(plugin.Deprecated); ok {\n\t\t\tif deprecated.DeprecationWarning() != \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Apply filter if provided\n\t\tif filter != nil && !filter(p) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Skip base.go plugin to avoid duplication with go plugin\n\t\tif strings.Contains(pluginKey, \"base.go.kubebuilder.io\") {\n\t\t\tcontinue\n\t\t}\n\n\t\t// For subcommands, skip default scaffold and its component plugins\n\t\tif excludeDefaultScaffold {\n\t\t\tif pluginKey == \"go.kubebuilder.io/v4\" ||\n\t\t\t\tpluginKey == \"kustomize.common.kubebuilder.io/v2\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tshortKey := getShortKey(pluginKey)\n\n\t\t// Get description from plugin if it implements Describable, otherwise use fallback\n\t\tvar desc string\n\t\tif describable, ok := p.(plugin.Describable); ok {\n\t\t\tdesc = describable.Description()\n\t\t} else {\n\t\t\tdesc = getPluginDescription(pluginKey)\n\t\t}\n\n\t\t// Get supported project versions\n\t\tsupportedVersions := p.SupportedProjectVersions()\n\t\tversionStrs := make([]string, 0, len(supportedVersions))\n\t\tfor _, ver := range supportedVersions {\n\t\t\tversionStrs = append(versionStrs, ver.String())\n\t\t}\n\t\tversionsStr := strings.Join(versionStrs, \", \")\n\n\t\tplugins = append(plugins, pluginInfo{\n\t\t\tshortKey:    shortKey,\n\t\t\tfullKey:     pluginKey,\n\t\t\tdescription: desc,\n\t\t\tversions:    versionsStr,\n\t\t})\n\t}\n\n\tif len(plugins) == 0 {\n\t\treturn \"No plugins available for this subcommand\"\n\t}\n\n\t// Sort by short key for better readability\n\tslices.SortFunc(plugins, func(a, b pluginInfo) int {\n\t\treturn strings.Compare(a.shortKey, b.shortKey)\n\t})\n\n\t// Calculate max width for KEY column\n\tmaxKeyWidth := len(\"KEY\")\n\tfor _, p := range plugins {\n\t\tif len(p.shortKey) > maxKeyWidth {\n\t\t\tmaxKeyWidth = len(p.shortKey)\n\t\t}\n\t}\n\n\t// Build aligned column output\n\tlines := make([]string, 0, len(plugins)+1)\n\t// Header\n\tlines = append(lines, fmt.Sprintf(\"  %-*s  %s\", maxKeyWidth, \"KEY\", \"DESCRIPTION\"))\n\t// Entries\n\tfor _, p := range plugins {\n\t\tlines = append(lines, fmt.Sprintf(\"  %-*s  %s\", maxKeyWidth, p.shortKey, p.description))\n\t}\n\n\treturn strings.Join(lines, \"\\n\")\n}\n"
  },
  {
    "path": "pkg/cli/suite_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nfunc TestCLI(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"CLI Suite\")\n}\n\n// Test plugin types and constructors.\nvar (\n\t_ plugin.Plugin = mockPlugin{}\n\t_ plugin.Plugin = mockDeprecatedPlugin{}\n)\n\ntype mockPlugin struct {\n\tname            string\n\tversion         plugin.Version\n\tprojectVersions []config.Version\n}\n\nfunc newMockPlugin(name, version string, projVers ...config.Version) plugin.Plugin {\n\tvar v plugin.Version\n\tif err := v.Parse(version); err != nil {\n\t\tpanic(err)\n\t}\n\treturn mockPlugin{name, v, projVers}\n}\n\nfunc (p mockPlugin) Name() string                               { return p.name }\nfunc (p mockPlugin) Version() plugin.Version                    { return p.version }\nfunc (p mockPlugin) SupportedProjectVersions() []config.Version { return p.projectVersions }\n\ntype mockDeprecatedPlugin struct {\n\tmockPlugin\n\tdeprecation string\n}\n\nfunc newMockDeprecatedPlugin(name, version, deprecation string, projVers ...config.Version) plugin.Plugin {\n\treturn mockDeprecatedPlugin{\n\t\tmockPlugin:  newMockPlugin(name, version, projVers...).(mockPlugin),\n\t\tdeprecation: deprecation,\n\t}\n}\n\nfunc (p mockDeprecatedPlugin) DeprecationWarning() string { return p.deprecation }\n"
  },
  {
    "path": "pkg/cli/version.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc (c CLI) newVersionCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:     \"version\",\n\t\tShort:   fmt.Sprintf(\"Print the %s version\", c.commandName),\n\t\tLong:    fmt.Sprintf(\"Print the %s version\", c.commandName),\n\t\tExample: fmt.Sprintf(\"%s version\", c.commandName),\n\t\tRunE: func(_ *cobra.Command, _ []string) error {\n\t\t\tfmt.Println(c.version)\n\t\t\treturn nil\n\t\t},\n\t}\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cli/version_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Version\", func() {\n\tvar c *CLI\n\n\tBeforeEach(func() {\n\t\tc = &CLI{}\n\t})\n\n\tContext(\"newVersionCmd\", func() {\n\t\tIt(\"Test the version\", func() {\n\t\t\tcmd := c.newVersionCmd()\n\t\t\tExpect(cmd).NotTo(BeNil())\n\t\t\tExpect(cmd.Use).To(ContainSubstring(\"version\"))\n\t\t\tExpect(cmd.Use).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Short).To(ContainSubstring(\"Print the\"))\n\t\t\tExpect(cmd.Long).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Long).To(ContainSubstring(\"Print the\"))\n\t\t\tExpect(cmd.Example).NotTo(Equal(\"\"))\n\t\t\tExpect(cmd.Example).To(ContainSubstring(\"version\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/cli/webhook.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nconst webhookErrorMsg = \"failed to create webhook\"\n\nfunc (c CLI) newCreateWebhookCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"webhook\",\n\t\tShort: \"Scaffold a webhook for an API resource\",\n\t\tLong:  `Scaffold a webhook for an API resource.`,\n\t\tRunE: errCmdFunc(\n\t\t\tfmt.Errorf(\"webhook subcommand requires an existing project\"),\n\t\t),\n\t}\n\n\t// In case no plugin was resolved, instead of failing the construction of the CLI, fail the execution of\n\t// this subcommand. This allows the use of subcommands that do not require resolved plugins like help.\n\tif len(c.resolvedPlugins) == 0 {\n\t\tcmdErr(cmd, noResolvedPluginError{})\n\t\treturn cmd\n\t}\n\n\t// Obtain the plugin keys and subcommands from the plugins that implement plugin.CreateWebhook.\n\tsubcommands := c.filterSubcommands(\n\t\tfunc(p plugin.Plugin) bool {\n\t\t\t_, isValid := p.(plugin.CreateWebhook)\n\t\t\treturn isValid\n\t\t},\n\t\tfunc(p plugin.Plugin) plugin.Subcommand {\n\t\t\treturn p.(plugin.CreateWebhook).GetCreateWebhookSubcommand()\n\t\t},\n\t)\n\n\t// Verify that there is at least one remaining plugin.\n\tif len(subcommands) == 0 {\n\t\tcmdErr(cmd, noAvailablePluginError{\"webhook creation\"})\n\t\treturn cmd\n\t}\n\n\tc.applySubcommandHooks(cmd, subcommands, webhookErrorMsg, false)\n\n\t// Append plugin table after metadata updates\n\tc.appendPluginTable(cmd, func(p plugin.Plugin) bool {\n\t\t_, isValid := p.(plugin.CreateWebhook)\n\t\treturn isValid\n\t}, \"Available plugins that support 'create webhook'\")\n\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/cli/webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cli\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"webhook\", func() {\n\tContext(\"constants\", func() {\n\t\tIt(\"should have correct error message\", func() {\n\t\t\tExpect(webhookErrorMsg).To(Equal(\"failed to create webhook\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/config/errors.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// UnsupportedVersionError is returned by New when a project configuration version is not supported.\ntype UnsupportedVersionError struct {\n\tVersion Version\n}\n\n// Error implements error interface\nfunc (e UnsupportedVersionError) Error() string {\n\treturn fmt.Sprintf(\"version %s is not supported\", e.Version)\n}\n\n// UnsupportedFieldError is returned when a project configuration version does not support\n// one of the fields as interface must be common for all the versions\ntype UnsupportedFieldError struct {\n\tVersion Version\n\tField   string\n}\n\n// Error implements error interface\nfunc (e UnsupportedFieldError) Error() string {\n\treturn fmt.Sprintf(\"version %s does not support the %s field\", e.Version, e.Field)\n}\n\n// ResourceNotFoundError is returned by Config.GetResource when the provided GVK cannot be found\ntype ResourceNotFoundError struct {\n\tGVK resource.GVK\n}\n\n// Error implements error interface\nfunc (e ResourceNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"resource %v could not be found\", e.GVK)\n}\n\n// PluginKeyNotFoundError is returned by Config.DecodePluginConfig when the provided key cannot be found\ntype PluginKeyNotFoundError struct {\n\tKey string\n}\n\n// Error implements error interface\nfunc (e PluginKeyNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"plugin key %q could not be found\", e.Key)\n}\n\n// MarshalError is returned by Config.Marshal when something went wrong while marshalling to YAML\ntype MarshalError struct {\n\tErr error\n}\n\n// Error implements error interface\nfunc (e MarshalError) Error() string {\n\treturn fmt.Sprintf(\"error marshalling project configuration: %v\", e.Err)\n}\n\n// Unwrap implements Wrapper interface\nfunc (e MarshalError) Unwrap() error {\n\treturn e.Err\n}\n\n// UnmarshalError is returned by Config.Unmarshal when something went wrong while unmarshalling from YAML\ntype UnmarshalError struct {\n\tErr error\n}\n\n// Error implements error interface\nfunc (e UnmarshalError) Error() string {\n\treturn fmt.Sprintf(\"error unmarshalling project configuration: %v\", e.Err)\n}\n\n// Unwrap implements Wrapper interface\nfunc (e UnmarshalError) Unwrap() error {\n\treturn e.Err\n}\n"
  },
  {
    "path": "pkg/config/errors_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"fmt\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"UnsupportedVersionError\", func() {\n\tvar err UnsupportedVersionError\n\n\tBeforeEach(func() {\n\t\terr = UnsupportedVersionError{\n\t\t\tVersion: Version{Number: 1},\n\t\t}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(\"version 1 is not supported\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"UnsupportedFieldError\", func() {\n\tvar err UnsupportedFieldError\n\n\tBeforeEach(func() {\n\t\terr = UnsupportedFieldError{\n\t\t\tVersion: Version{Number: 1},\n\t\t\tField:   \"name\",\n\t\t}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(\"version 1 does not support the name field\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"ResourceNotFoundError\", func() {\n\tvar err ResourceNotFoundError\n\n\tBeforeEach(func() {\n\t\terr = ResourceNotFoundError{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"group\",\n\t\t\t\tDomain:  \"my.domain\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Kind\",\n\t\t\t},\n\t\t}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(\"resource {group my.domain v1 Kind} could not be found\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"PluginKeyNotFoundError\", func() {\n\tvar err PluginKeyNotFoundError\n\n\tBeforeEach(func() {\n\t\terr = PluginKeyNotFoundError{\n\t\t\tKey: \"go.kubebuilder.io/v1\",\n\t\t}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(\"plugin key \\\"go.kubebuilder.io/v1\\\" could not be found\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"MarshalError\", func() {\n\tvar (\n\t\twrapped error\n\t\terr     MarshalError\n\t)\n\n\tBeforeEach(func() {\n\t\twrapped = fmt.Errorf(\"wrapped error\")\n\t\terr = MarshalError{Err: wrapped}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(fmt.Sprintf(\"error marshalling project configuration: %v\", wrapped)))\n\t\t})\n\t})\n\n\tContext(\"Unwrap\", func() {\n\t\tIt(\"should unwrap to the wrapped error\", func() {\n\t\t\tExpect(err.Unwrap()).To(Equal(wrapped))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"UnmarshalError\", func() {\n\tvar (\n\t\twrapped error\n\t\terr     UnmarshalError\n\t)\n\n\tBeforeEach(func() {\n\t\twrapped = fmt.Errorf(\"wrapped error\")\n\t\terr = UnmarshalError{Err: wrapped}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(fmt.Sprintf(\"error unmarshalling project configuration: %v\", wrapped)))\n\t\t})\n\t})\n\n\tContext(\"Unwrap\", func() {\n\t\tIt(\"should unwrap to the wrapped error\", func() {\n\t\t\tExpect(err.Unwrap()).To(Equal(wrapped))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/config/interface.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// Config defines the interface that project configuration types must follow.\ntype Config interface {\n\t/* Version */\n\n\t// GetVersion returns the current project version.\n\tGetVersion() Version\n\n\t// GetCliVersion returns the CLI binary version that was used to scaffold or initialize the project.\n\tGetCliVersion() string\n\n\t// SetCliVersion sets the binary version used to initialize the project.\n\tSetCliVersion(version string) error\n\n\t/* String fields */\n\n\t// GetDomain returns the project domain.\n\tGetDomain() string\n\t// SetDomain sets the project domain.\n\tSetDomain(domain string) error\n\n\t// GetRepository returns the project repository.\n\tGetRepository() string\n\t// SetRepository sets the project repository.\n\tSetRepository(repository string) error\n\n\t// GetProjectName returns the project name.\n\t// This method was introduced in project version 3.\n\tGetProjectName() string\n\t// SetProjectName sets the project name.\n\t// This method was introduced in project version 3.\n\tSetProjectName(name string) error\n\n\t// GetPluginChain returns the plugin chain.\n\t// This method was introduced in project version 3.\n\tGetPluginChain() []string\n\t// SetPluginChain sets the plugin chain.\n\t// This method was introduced in project version 3.\n\tSetPluginChain(pluginChain []string) error\n\n\t/* Boolean fields */\n\n\t// IsMultiGroup checks if multi-group is enabled.\n\tIsMultiGroup() bool\n\t// SetMultiGroup enables multi-group.\n\tSetMultiGroup() error\n\t// ClearMultiGroup disables multi-group.\n\tClearMultiGroup() error\n\n\t// IsNamespaced checks if the project is configured for namespace-scoped deployment.\n\tIsNamespaced() bool\n\t// SetNamespaced enables namespace-scoped deployment.\n\tSetNamespaced() error\n\t// ClearNamespaced disables namespace-scoped deployment (default: cluster-scoped).\n\tClearNamespaced() error\n\n\t/* Resources */\n\n\t// ResourcesLength returns the number of tracked resources.\n\tResourcesLength() int\n\t// HasResource checks if the provided GVK is stored in the Config.\n\tHasResource(gvk resource.GVK) bool\n\t// GetResource returns the stored resource matching the provided GVK.\n\tGetResource(gvk resource.GVK) (resource.Resource, error)\n\t// GetResources returns all the stored resources.\n\tGetResources() ([]resource.Resource, error)\n\t// AddResource adds the provided resource if it was not present, no-op if it was already present.\n\tAddResource(res resource.Resource) error\n\t// UpdateResource adds the provided resource if it was not present, modifies it if it was already present.\n\tUpdateResource(res resource.Resource) error\n\n\t// HasGroup checks if the provided group is the same as any of the tracked resources.\n\tHasGroup(group string) bool\n\t// ListCRDVersions returns a list of the CRD versions in use by the tracked resources.\n\tListCRDVersions() []string\n\t// ListWebhookVersions returns a list of the webhook versions in use by the tracked resources.\n\tListWebhookVersions() []string\n\n\t/* Plugins */\n\n\t// DecodePluginConfig decodes a plugin config stored in Config into configObj, which must be a pointer.\n\t// This method is intended to be used for custom configuration objects, which were introduced in project version 3.\n\tDecodePluginConfig(key string, configObj any) error\n\t// EncodePluginConfig encodes a config object into Config by overwriting the existing object stored under key.\n\t// This method is intended to be used for custom configuration objects, which were introduced in project version 3.\n\tEncodePluginConfig(key string, configObj any) error\n\n\t/* Persistence */\n\n\t// MarshalYAML Marshal returns the YAML representation of the Config.\n\tMarshalYAML() ([]byte, error)\n\t// UnmarshalYAML Unmarshal loads the Config fields from its YAML representation.\n\tUnmarshalYAML([]byte) error\n}\n"
  },
  {
    "path": "pkg/config/registry.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nvar registry = make(map[Version]func() Config)\n\n// Register allows implementations of Config to register themselves so that they can be created with New\nfunc Register(version Version, constructor func() Config) {\n\tregistry[version] = constructor\n}\n\n// IsRegistered returns true if the given version has been registered through Register\nfunc IsRegistered(version Version) bool {\n\t_, ok := registry[version]\n\treturn ok\n}\n\n// New creates Config instances from the previously registered implementations through Register\nfunc New(version Version) (Config, error) {\n\tif constructor, exists := registry[version]; exists {\n\t\treturn constructor(), nil\n\t}\n\n\treturn nil, UnsupportedVersionError{Version: version}\n}\n"
  },
  {
    "path": "pkg/config/registry_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"registry\", func() {\n\tvar (\n\t\tversion Version\n\t\tf       func() Config\n\t)\n\n\tBeforeEach(func() {\n\t\tversion = Version{}\n\t\tf = func() Config { return nil }\n\t})\n\n\tAfterEach(func() {\n\t\tregistry = make(map[Version]func() Config)\n\t})\n\n\tContext(\"Register\", func() {\n\t\tIt(\"should register new constructors\", func() {\n\t\t\tRegister(version, f)\n\t\t\tExpect(registry).To(HaveKey(version))\n\t\t\tExpect(registry[version]()).To(BeNil())\n\t\t})\n\t})\n\n\tContext(\"IsRegistered\", func() {\n\t\tIt(\"should return true for registered constructors\", func() {\n\t\t\tRegister(version, f)\n\t\t\tExpect(IsRegistered(version)).To(BeTrue())\n\t\t})\n\t\tIt(\"should fail for unregistered constructors\", func() {\n\t\t\tExpect(IsRegistered(version)).To(BeFalse())\n\t\t})\n\t})\n\n\tContext(\"New\", func() {\n\t\tIt(\"should use the registered constructors\", func() {\n\t\t\tregistry[version] = f\n\t\t\tresult, err := New(version)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(BeNil())\n\t\t})\n\n\t\tIt(\"should fail for unregistered constructors\", func() {\n\t\t\t_, err := New(version)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/config/store/errors.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n)\n\n// LoadError wraps errors yielded by Store.Load and Store.LoadFrom methods\ntype LoadError struct {\n\tErr error\n}\n\n// Error implements error interface\nfunc (e LoadError) Error() string {\n\treturn fmt.Sprintf(\"unable to load the configuration: %v\", e.Err)\n}\n\n// Unwrap implements Wrapper interface\nfunc (e LoadError) Unwrap() error {\n\treturn e.Err\n}\n\n// SaveError wraps errors yielded by Store.Save and Store.SaveTo methods\ntype SaveError struct {\n\tErr error\n}\n\n// Error implements error interface\nfunc (e SaveError) Error() string {\n\treturn fmt.Sprintf(\"unable to save the configuration: %v\", e.Err)\n}\n\n// Unwrap implements Wrapper interface\nfunc (e SaveError) Unwrap() error {\n\treturn e.Err\n}\n"
  },
  {
    "path": "pkg/config/store/errors_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestConfigStore(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Config Store Suite\")\n}\n\nvar _ = Describe(\"LoadError\", func() {\n\tvar (\n\t\twrapped error\n\t\terr     LoadError\n\t)\n\n\tBeforeEach(func() {\n\t\twrapped = fmt.Errorf(\"error message\")\n\t\terr = LoadError{Err: wrapped}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(fmt.Sprintf(\"unable to load the configuration: %v\", wrapped)))\n\t\t})\n\t})\n\n\tContext(\"Unwrap\", func() {\n\t\tIt(\"should unwrap to the wrapped error\", func() {\n\t\t\tExpect(err.Unwrap()).To(Equal(wrapped))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"SaveError\", func() {\n\tvar (\n\t\twrapped error\n\t\terr     SaveError\n\t)\n\n\tBeforeEach(func() {\n\t\twrapped = fmt.Errorf(\"error message\")\n\t\terr = SaveError{Err: wrapped}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(fmt.Sprintf(\"unable to save the configuration: %v\", wrapped)))\n\t\t})\n\t})\n\n\tContext(\"Unwrap\", func() {\n\t\tIt(\"should unwrap to the wrapped error\", func() {\n\t\t\tExpect(err.Unwrap()).To(Equal(wrapped))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/config/store/interface.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage store\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\n// Store represents a persistence backend for config.Config\ntype Store interface {\n\t// New creates a new config.Config to store\n\tNew(config.Version) error\n\t// Load retrieves the config.Config from the persistence backend\n\tLoad() error\n\t// LoadFrom retrieves the config.Config from the persistence backend at the specified key\n\tLoadFrom(string) error\n\t// Save stores the config.Config into the persistence backend\n\tSave() error\n\t// SaveTo stores the config.Config into the persistence backend at the specified key\n\tSaveTo(string) error\n\n\t// Config returns the stored config.Config\n\tConfig() config.Config\n}\n"
  },
  {
    "path": "pkg/config/store/yaml/store.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage yaml\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/afero\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst (\n\t// DefaultPath is the default path for the configuration file\n\tDefaultPath = \"PROJECT\"\n\n\t// Comment for 'PROJECT' config file\n\tcommentStr = `# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\n`\n)\n\n// yamlStore implements store.Store using a YAML file as the storage backend\n// The key is translated into the YAML file path\ntype yamlStore struct {\n\t// fs is the filesystem that will be used to store the config.Config\n\tfs afero.Fs\n\t// mustNotExist requires the file not to exist when saving it\n\tmustNotExist bool\n\n\tcfg config.Config\n}\n\n// New creates a new configuration that will be stored at the provided path\nfunc New(fs machinery.Filesystem) store.Store {\n\treturn &yamlStore{fs: fs.FS}\n}\n\n// New implements store.Store interface\nfunc (s *yamlStore) New(version config.Version) error {\n\tcfg, err := config.New(version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create config: %w\", err)\n\t}\n\n\ts.cfg = cfg\n\ts.mustNotExist = true\n\treturn nil\n}\n\n// Load implements store.Store interface\nfunc (s *yamlStore) Load() error {\n\treturn s.LoadFrom(DefaultPath)\n}\n\ntype versionedConfig struct {\n\tVersion config.Version `json:\"version\"`\n}\n\n// LoadFrom implements store.Store interface\nfunc (s *yamlStore) LoadFrom(path string) error {\n\ts.mustNotExist = false\n\n\t// Read the file\n\tin, err := afero.ReadFile(s.fs, path)\n\tif err != nil {\n\t\treturn store.LoadError{Err: fmt.Errorf(\"failed to read %q file: %w\", path, err)}\n\t}\n\n\t// Check the file version\n\tvar versioned versionedConfig\n\tif err = yaml.Unmarshal(in, &versioned); err != nil {\n\t\treturn store.LoadError{Err: fmt.Errorf(\"failed to determine config version: %w\", err)}\n\t}\n\n\t// Create the config object\n\tvar cfg config.Config\n\tcfg, err = config.New(versioned.Version)\n\tif err != nil {\n\t\treturn store.LoadError{Err: fmt.Errorf(\"failed to create config for version %q: %w\", versioned.Version, err)}\n\t}\n\n\t// Unmarshal the file content\n\tif err := cfg.UnmarshalYAML(in); err != nil {\n\t\treturn store.LoadError{Err: fmt.Errorf(\"failed to unmarshal config at %q: %w\", path, err)}\n\t}\n\n\ts.cfg = cfg\n\treturn nil\n}\n\n// Save implements store.Store interface\nfunc (s yamlStore) Save() error {\n\treturn s.SaveTo(DefaultPath)\n}\n\n// SaveTo implements store.Store interface\nfunc (s yamlStore) SaveTo(path string) error {\n\t// If yamlStore is unset, none of New, Load, or LoadFrom were called successfully\n\tif s.cfg == nil {\n\t\treturn store.SaveError{Err: fmt.Errorf(\"undefined config, use one of the initializers: New, Load, LoadFrom\")}\n\t}\n\n\t// If it is a new configuration, the path should not exist yet\n\tif s.mustNotExist {\n\t\t// Check that the file doesn't exist\n\t\t_, err := s.fs.Stat(path)\n\t\tif err == nil || os.IsExist(err) {\n\t\t\t// File already exists\n\t\t\treturn store.SaveError{Err: fmt.Errorf(\"configuration already exists in %q\", path)}\n\t\t} else if !os.IsNotExist(err) {\n\t\t\t// Error occurred while checking file existence\n\t\t\treturn store.SaveError{Err: fmt.Errorf(\"failed to check for file prior existence: %w\", err)}\n\t\t}\n\t}\n\n\t// Marshall into YAML\n\tcontent, err := s.cfg.MarshalYAML()\n\tif err != nil {\n\t\treturn store.SaveError{Err: fmt.Errorf(\"failed to marshal to YAML: %w\", err)}\n\t}\n\n\t// Prepend warning comment for the 'PROJECT' file\n\tcontent = append([]byte(commentStr), content...)\n\n\t// Write the marshalled configuration\n\terr = afero.WriteFile(s.fs, path, content, machinery.DefaultFilePermission)\n\tif err != nil {\n\t\treturn store.SaveError{Err: fmt.Errorf(\"failed to save configuration to %q: %w\", path, err)}\n\t}\n\n\treturn nil\n}\n\n// Config implements store.Store interface\nfunc (s yamlStore) Config() config.Config {\n\treturn s.cfg\n}\n"
  },
  {
    "path": "pkg/config/store/yaml/store_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage yaml\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nfunc TestConfigStoreYaml(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Config Store YAML Suite\")\n}\n\nvar _ = Describe(\"New\", func() {\n\tIt(\"should return a new empty store\", func() {\n\t\ts := New(machinery.Filesystem{FS: afero.NewMemMapFs()})\n\t\tExpect(s.Config()).To(BeNil())\n\n\t\tys, ok := s.(*yamlStore)\n\t\tExpect(ok).To(BeTrue())\n\t\tExpect(ys.fs).NotTo(BeNil())\n\t})\n})\n\nvar _ = Describe(\"yamlStore\", func() {\n\tconst (\n\t\tv3File = `version: \"3\"\n`\n\t\tunversionedFile = `version:\n`\n\t\tnonexistentVersionFile = `version: 1-alpha\n` // v1-alpha never existed\n\t\twrongFile = `version: \"2\"\nlayout: \"\"\n` // layout field does not exist in v2\n\t)\n\n\tvar (\n\t\ts    *yamlStore\n\t\tpath string\n\t)\n\n\tBeforeEach(func() {\n\t\ts = New(machinery.Filesystem{FS: afero.NewMemMapFs()}).(*yamlStore)\n\t\tpath = DefaultPath + \"2\"\n\t})\n\n\tContext(\"New\", func() {\n\t\tIt(\"should fail for an unregistered config version\", func() {\n\t\t\tExpect(s.New(config.Version{})).NotTo(Succeed())\n\t\t})\n\t})\n\n\tContext(\"Load\", func() {\n\t\tIt(\"should load the Config from an existing file at the default path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, DefaultPath, []byte(commentStr+v3File), os.ModePerm)).To(Succeed())\n\n\t\t\tExpect(s.Load()).To(Succeed())\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.mustNotExist).To(BeFalse())\n\t\t\tExpect(s.Config()).NotTo(BeNil())\n\t\t\tExpect(s.Config().GetVersion().Compare(cfgv3.Version)).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should fail if no file exists at the default path\", func() {\n\t\t\terr := s.Load()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to read %q file: %w\", DefaultPath, &os.PathError{\n\t\t\t\t\tErr:  os.ErrNotExist,\n\t\t\t\t\tPath: DefaultPath,\n\t\t\t\t\tOp:   \"open\",\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to identify the version of the file at the default path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, DefaultPath, []byte(commentStr+unversionedFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.Load()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to determine config version: %w\",\n\t\t\t\t\tfmt.Errorf(\"error unmarshaling JSON: %w\",\n\t\t\t\t\t\tfmt.Errorf(\"while decoding JSON: %w\",\n\t\t\t\t\t\t\terrors.New(\"project version is empty\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to create a Config for the version of the file at the default path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, DefaultPath, []byte(commentStr+nonexistentVersionFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.Load()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to create config for version %q: %w\", \"1-alpha\", config.UnsupportedVersionError{\n\t\t\t\t\tVersion: config.Version{Number: 1, Stage: 2},\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to unmarshal the file at the default path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, DefaultPath, []byte(commentStr+wrongFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.Load()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to create config for version %q: %w\", \"2\", config.UnsupportedVersionError{\n\t\t\t\t\tVersion: config.Version{\n\t\t\t\t\t\tNumber: 2,\n\t\t\t\t\t\tStage:  0,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\t})\n\n\tContext(\"LoadFrom\", func() {\n\t\tIt(\"should load the Config from an existing file from the specified path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(commentStr+v3File), os.ModePerm)).To(Succeed())\n\n\t\t\tExpect(s.LoadFrom(path)).To(Succeed())\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.mustNotExist).To(BeFalse())\n\t\t\tExpect(s.Config()).NotTo(BeNil())\n\t\t\tExpect(s.Config().GetVersion().Compare(cfgv3.Version)).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should fail if no file exists at the specified path\", func() {\n\t\t\terr := s.LoadFrom(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to read %q file: %w\", path, &os.PathError{\n\t\t\t\t\tErr:  os.ErrNotExist,\n\t\t\t\t\tPath: path,\n\t\t\t\t\tOp:   \"open\",\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to identify the version of the file at the specified path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(commentStr+unversionedFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.LoadFrom(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to determine config version: %w\",\n\t\t\t\t\tfmt.Errorf(\"error unmarshaling JSON: %w\",\n\t\t\t\t\t\tfmt.Errorf(\"while decoding JSON: %w\",\n\t\t\t\t\t\t\terrors.New(\"project version is empty\"),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to create a Config for the version of the file at the specified path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(commentStr+nonexistentVersionFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.LoadFrom(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to create config for version %q: %w\", \"1-alpha\", config.UnsupportedVersionError{\n\t\t\t\t\tVersion: config.Version{Number: 1, Stage: 2},\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail if unable to unmarshal the file at the specified path\", func() {\n\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(commentStr+wrongFile), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.LoadFrom(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.LoadError{\n\t\t\t\tErr: fmt.Errorf(\"failed to create config for version %q: %w\", \"2\", config.UnsupportedVersionError{\n\t\t\t\t\tVersion: config.Version{\n\t\t\t\t\t\tNumber: 2,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t}))\n\t\t})\n\t})\n\n\tContext(\"Save\", func() {\n\t\tIt(\"should succeed for a valid config\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\tExpect(s.Save()).To(Succeed())\n\n\t\t\tcfgBytes, err := afero.ReadFile(s.fs, DefaultPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(cfgBytes)).To(Equal(commentStr + v3File))\n\t\t})\n\n\t\tIt(\"should succeed for a valid config that must not exist\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\ts.mustNotExist = true\n\t\t\tExpect(s.Save()).To(Succeed())\n\n\t\t\tcfgBytes, err := afero.ReadFile(s.fs, DefaultPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(cfgBytes)).To(Equal(commentStr + v3File))\n\t\t})\n\n\t\tIt(\"should fail for an empty config\", func() {\n\t\t\terr := s.Save()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.SaveError{\n\t\t\t\tErr: errors.New(\"undefined config, use one of the initializers: New, Load, LoadFrom\"),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail for a pre-existent file that must not exist\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\ts.mustNotExist = true\n\t\t\tExpect(afero.WriteFile(s.fs, DefaultPath, []byte(v3File), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.Save()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.SaveError{\n\t\t\t\tErr: fmt.Errorf(\"configuration already exists in %q\", DefaultPath),\n\t\t\t}))\n\t\t})\n\t})\n\n\tContext(\"SaveTo\", func() {\n\t\tIt(\"should success for valid configs\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\tExpect(s.SaveTo(path)).To(Succeed())\n\n\t\t\tcfgBytes, err := afero.ReadFile(s.fs, path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(cfgBytes)).To(Equal(commentStr + v3File))\n\t\t})\n\n\t\tIt(\"should succeed for a valid config that must not exist\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\ts.mustNotExist = true\n\t\t\tExpect(s.SaveTo(path)).To(Succeed())\n\n\t\t\tcfgBytes, err := afero.ReadFile(s.fs, path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(cfgBytes)).To(Equal(commentStr + v3File))\n\t\t})\n\n\t\tIt(\"should fail for an empty config\", func() {\n\t\t\terr := s.SaveTo(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.SaveError{\n\t\t\t\tErr: errors.New(\"undefined config, use one of the initializers: New, Load, LoadFrom\"),\n\t\t\t}))\n\t\t})\n\n\t\tIt(\"should fail for a pre-existent file that must not exist\", func() {\n\t\t\ts.cfg = cfgv3.New()\n\t\t\ts.mustNotExist = true\n\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(v3File), os.ModePerm)).To(Succeed())\n\n\t\t\terr := s.SaveTo(path)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(store.SaveError{\n\t\t\t\tErr: fmt.Errorf(\"configuration already exists in %q\", path),\n\t\t\t}))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/config/suite_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestConfig(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Config Suite\")\n}\n"
  },
  {
    "path": "pkg/config/v3/config.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v3\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// Version is the config.Version for project configuration 3\nvar Version = config.Version{Number: 3}\n\n// stringSlice is a []string but that can also be unmarshalled from a single string,\n// which is introduced as the first and only element of the slice\n// It is used to offer backwards compatibility as the field used to be a string.\ntype stringSlice []string\n\nfunc (ss *stringSlice) UnmarshalJSON(b []byte) error {\n\tif b[0] == '[' {\n\t\tvar sl []string\n\t\tif err := yaml.Unmarshal(b, &sl); err != nil {\n\t\t\treturn fmt.Errorf(\"error unmarshalling string slice %q: %w\", sl, err)\n\t\t}\n\t\t*ss = sl\n\t\treturn nil\n\t}\n\n\tvar st string\n\tif err := yaml.Unmarshal(b, &st); err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling string %q: %w\", st, err)\n\t}\n\t*ss = stringSlice{st}\n\treturn nil\n}\n\n// Cfg defines the Project Config (PROJECT file)\ntype Cfg struct {\n\t// Version\n\tVersion config.Version `json:\"version\"`\n\n\t// String fields\n\tDomain      string      `json:\"domain,omitempty\"`\n\tRepository  string      `json:\"repo,omitempty\"`\n\tName        string      `json:\"projectName,omitempty\"`\n\tCliVersion  string      `json:\"cliVersion,omitempty\"`\n\tPluginChain stringSlice `json:\"layout,omitempty\"`\n\n\t// Boolean fields\n\tMultiGroup bool `json:\"multigroup,omitempty\"`\n\tNamespaced bool `json:\"namespaced,omitempty\"`\n\n\t// Resources\n\tResources []resource.Resource `json:\"resources,omitempty\"`\n\n\t// Plugins\n\tPlugins pluginConfigs `json:\"plugins,omitempty\"`\n}\n\n// pluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key.\ntype pluginConfigs map[string]pluginConfig\n\n// pluginConfig is an arbitrary plugin configuration object.\ntype pluginConfig any\n\n// New returns a new config.Config\nfunc New() config.Config {\n\treturn &Cfg{Version: Version}\n}\n\nfunc init() {\n\tconfig.Register(Version, New)\n}\n\n// GetVersion implements config.Config\nfunc (c Cfg) GetVersion() config.Version {\n\treturn c.Version\n}\n\n// GetCliVersion implements config.Config\nfunc (c Cfg) GetCliVersion() string {\n\treturn c.CliVersion\n}\n\n// SetCliVersion implements config.Config\nfunc (c *Cfg) SetCliVersion(version string) error {\n\tc.CliVersion = version\n\treturn nil\n}\n\n// GetDomain implements config.Config\nfunc (c Cfg) GetDomain() string {\n\treturn c.Domain\n}\n\n// SetDomain implements config.Config\nfunc (c *Cfg) SetDomain(domain string) error {\n\tc.Domain = domain\n\treturn nil\n}\n\n// GetRepository implements config.Config\nfunc (c Cfg) GetRepository() string {\n\treturn c.Repository\n}\n\n// SetRepository implements config.Config\nfunc (c *Cfg) SetRepository(repository string) error {\n\tc.Repository = repository\n\treturn nil\n}\n\n// GetProjectName implements config.Config\nfunc (c Cfg) GetProjectName() string {\n\treturn c.Name\n}\n\n// SetProjectName implements config.Config\nfunc (c *Cfg) SetProjectName(name string) error {\n\tc.Name = name\n\treturn nil\n}\n\n// GetPluginChain implements config.Config\nfunc (c Cfg) GetPluginChain() []string {\n\treturn c.PluginChain\n}\n\n// SetPluginChain implements config.Config\nfunc (c *Cfg) SetPluginChain(pluginChain []string) error {\n\tc.PluginChain = pluginChain\n\treturn nil\n}\n\n// IsMultiGroup implements config.Config\nfunc (c Cfg) IsMultiGroup() bool {\n\treturn c.MultiGroup\n}\n\n// SetMultiGroup implements config.Config\nfunc (c *Cfg) SetMultiGroup() error {\n\tc.MultiGroup = true\n\treturn nil\n}\n\n// ClearMultiGroup implements config.Config\nfunc (c *Cfg) ClearMultiGroup() error {\n\tc.MultiGroup = false\n\treturn nil\n}\n\n// IsNamespaced implements config.Config\nfunc (c Cfg) IsNamespaced() bool {\n\treturn c.Namespaced\n}\n\n// SetNamespaced implements config.Config\nfunc (c *Cfg) SetNamespaced() error {\n\tc.Namespaced = true\n\treturn nil\n}\n\n// ClearNamespaced implements config.Config\nfunc (c *Cfg) ClearNamespaced() error {\n\tc.Namespaced = false\n\treturn nil\n}\n\n// ResourcesLength implements config.Config\nfunc (c Cfg) ResourcesLength() int {\n\treturn len(c.Resources)\n}\n\n// HasResource implements config.Config\nfunc (c Cfg) HasResource(gvk resource.GVK) bool {\n\tfound := false\n\tfor _, res := range c.Resources {\n\t\tif gvk.IsEqualTo(res.GVK) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn found\n}\n\n// GetResource implements config.Config\nfunc (c Cfg) GetResource(gvk resource.GVK) (resource.Resource, error) {\n\tfor _, res := range c.Resources {\n\t\tif gvk.IsEqualTo(res.GVK) {\n\t\t\tr := res.Copy()\n\n\t\t\t// Plural is only stored if irregular, so if it is empty recover the regular form\n\t\t\tif r.Plural == \"\" {\n\t\t\t\tr.Plural = resource.RegularPlural(r.Kind)\n\t\t\t}\n\n\t\t\treturn r, nil\n\t\t}\n\t}\n\n\treturn resource.Resource{}, config.ResourceNotFoundError{GVK: gvk}\n}\n\n// GetResources implements config.Config\nfunc (c Cfg) GetResources() ([]resource.Resource, error) {\n\tresources := make([]resource.Resource, 0, len(c.Resources))\n\tfor _, res := range c.Resources {\n\t\tr := res.Copy()\n\n\t\t// Plural is only stored if irregular, so if it is empty recover the regular form\n\t\tif r.Plural == \"\" {\n\t\t\tr.Plural = resource.RegularPlural(r.Kind)\n\t\t}\n\n\t\tresources = append(resources, r)\n\t}\n\n\treturn resources, nil\n}\n\n// AddResource implements config.Config\nfunc (c *Cfg) AddResource(res resource.Resource) error {\n\t// As res is passed by value it is already a shallow copy, but we need to make a deep copy\n\tres = res.Copy()\n\n\t// Plural is only stored if irregular\n\tif res.Plural == resource.RegularPlural(res.Kind) {\n\t\tres.Plural = \"\"\n\t}\n\n\tif !c.HasResource(res.GVK) {\n\t\tc.Resources = append(c.Resources, res)\n\t}\n\treturn nil\n}\n\n// UpdateResource implements config.Config\nfunc (c *Cfg) UpdateResource(res resource.Resource) error {\n\t// As res is passed by value it is already a shallow copy, but we need to make a deep copy\n\tres = res.Copy()\n\n\t// Plural is only stored if irregular\n\tif res.Plural == resource.RegularPlural(res.Kind) {\n\t\tres.Plural = \"\"\n\t}\n\n\tfor i, r := range c.Resources {\n\t\tif res.IsEqualTo(r.GVK) {\n\t\t\tif err := c.Resources[i].Update(res); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to update resource %q: %w\", res.GVK, err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tc.Resources = append(c.Resources, res)\n\treturn nil\n}\n\n// HasGroup implements config.Config\nfunc (c Cfg) HasGroup(group string) bool {\n\t// Return true if the target group is found in the tracked resources\n\tfor _, r := range c.Resources {\n\t\tif strings.EqualFold(group, r.Group) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Return false otherwise\n\treturn false\n}\n\n// ListCRDVersions implements config.Config\nfunc (c Cfg) ListCRDVersions() []string {\n\t// Make a map to remove duplicates\n\tversionSet := make(map[string]struct{})\n\tfor _, r := range c.Resources {\n\t\tif r.API != nil && r.API.CRDVersion != \"\" {\n\t\t\tversionSet[r.API.CRDVersion] = struct{}{}\n\t\t}\n\t}\n\n\t// Convert the map into a slice\n\tversions := make([]string, 0, len(versionSet))\n\tfor version := range versionSet {\n\t\tversions = append(versions, version)\n\t}\n\treturn versions\n}\n\n// ListWebhookVersions implements config.Config\nfunc (c Cfg) ListWebhookVersions() []string {\n\t// Make a map to remove duplicates\n\tversionSet := make(map[string]struct{})\n\tfor _, r := range c.Resources {\n\t\tif r.Webhooks != nil && r.Webhooks.WebhookVersion != \"\" {\n\t\t\tversionSet[r.Webhooks.WebhookVersion] = struct{}{}\n\t\t}\n\t}\n\n\t// Convert the map into a slice\n\tversions := make([]string, 0, len(versionSet))\n\tfor version := range versionSet {\n\t\tversions = append(versions, version)\n\t}\n\treturn versions\n}\n\n// DecodePluginConfig implements config.Config\nfunc (c Cfg) DecodePluginConfig(key string, configObj any) error {\n\tif len(c.Plugins) == 0 {\n\t\treturn config.PluginKeyNotFoundError{Key: key}\n\t}\n\n\t// Get the object blob by key and unmarshal into the object.\n\tif pluginCfg, hasKey := c.Plugins[key]; hasKey {\n\t\tb, err := yaml.Marshal(pluginCfg)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to convert extra fields object to bytes: %w\", err)\n\t\t}\n\t\tif err := yaml.Unmarshal(b, configObj); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal extra fields object: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn config.PluginKeyNotFoundError{Key: key}\n}\n\n// EncodePluginConfig will return an error if used on any project version < v3.\nfunc (c *Cfg) EncodePluginConfig(key string, configObj any) error {\n\t// Get object's bytes and set them under key in extra fields.\n\tb, err := yaml.Marshal(configObj)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to convert %T object to bytes: %w\", configObj, err)\n\t}\n\tvar fields map[string]any\n\tif err := yaml.Unmarshal(b, &fields); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal %T object bytes: %w\", configObj, err)\n\t}\n\tif c.Plugins == nil {\n\t\tc.Plugins = make(map[string]pluginConfig)\n\t}\n\tc.Plugins[key] = fields\n\treturn nil\n}\n\n// MarshalYAML implements config.Config\nfunc (c Cfg) MarshalYAML() ([]byte, error) {\n\tfor i, r := range c.Resources {\n\t\t// If API is empty, omit it (prevents `api: {}`).\n\t\tif r.API != nil && r.API.IsEmpty() {\n\t\t\tc.Resources[i].API = nil\n\t\t}\n\t\t// If Webhooks is empty, omit it (prevents `webhooks: {}`).\n\t\tif r.Webhooks != nil && r.Webhooks.IsEmpty() {\n\t\t\tc.Resources[i].Webhooks = nil\n\t\t}\n\t}\n\n\tcontent, err := yaml.Marshal(c)\n\tif err != nil {\n\t\treturn nil, config.MarshalError{Err: err}\n\t}\n\n\treturn content, nil\n}\n\n// UnmarshalYAML implements config.Config\nfunc (c *Cfg) UnmarshalYAML(b []byte) error {\n\t// Use non-strict unmarshaling to allow forward compatibility and external plugin fields.\n\t// Older versions of kubebuilder should be able to read PROJECT files\n\t// with newer fields and simply ignore unknown fields.\n\tif err := yaml.Unmarshal(b, c); err != nil {\n\t\treturn config.UnmarshalError{Err: err}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/config/v3/config_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v3\n\nimport (\n\t\"slices\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nfunc TestConfigV3(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Config V3 Suite\")\n}\n\nvar _ = Describe(\"Cfg\", func() {\n\tconst (\n\t\tdomain = \"my.domain\"\n\t\trepo   = \"myrepo\"\n\t\tname   = \"ProjectName\"\n\n\t\totherDomain = \"other.domain\"\n\t\totherRepo   = \"otherrepo\"\n\t\totherName   = \"OtherProjectName\"\n\t)\n\n\tvar (\n\t\tc                Cfg\n\t\tpluginChain      []string\n\t\totherPluginChain []string\n\t)\n\n\tBeforeEach(func() {\n\t\tpluginChain = []string{\"go.kubebuilder.io/v2\"}\n\t\totherPluginChain = []string{\"go.kubebuilder.io/v3\"}\n\n\t\tc = Cfg{\n\t\t\tVersion:     Version,\n\t\t\tDomain:      domain,\n\t\t\tRepository:  repo,\n\t\t\tName:        name,\n\t\t\tPluginChain: pluginChain,\n\t\t}\n\t})\n\n\tContext(\"Version\", func() {\n\t\tIt(\"GetVersion should return version 3\", func() {\n\t\t\tExpect(c.GetVersion().Compare(Version)).To(Equal(0))\n\t\t})\n\t})\n\n\tContext(\"Domain\", func() {\n\t\tIt(\"GetDomain should return the domain\", func() {\n\t\t\tExpect(c.GetDomain()).To(Equal(domain))\n\t\t})\n\n\t\tIt(\"SetDomain should set the domain\", func() {\n\t\t\tExpect(c.SetDomain(otherDomain)).To(Succeed())\n\t\t\tExpect(c.Domain).To(Equal(otherDomain))\n\t\t})\n\t})\n\n\tContext(\"Repository\", func() {\n\t\tIt(\"GetRepository should return the repository\", func() {\n\t\t\tExpect(c.GetRepository()).To(Equal(repo))\n\t\t})\n\n\t\tIt(\"SetRepository should set the repository\", func() {\n\t\t\tExpect(c.SetRepository(otherRepo)).To(Succeed())\n\t\t\tExpect(c.Repository).To(Equal(otherRepo))\n\t\t})\n\t})\n\n\tContext(\"Project name\", func() {\n\t\tIt(\"GetProjectName should return the name\", func() {\n\t\t\tExpect(c.GetProjectName()).To(Equal(name))\n\t\t})\n\n\t\tIt(\"SetProjectName should set the name\", func() {\n\t\t\tExpect(c.SetProjectName(otherName)).To(Succeed())\n\t\t\tExpect(c.Name).To(Equal(otherName))\n\t\t})\n\t})\n\n\tContext(\"Plugin chain\", func() {\n\t\tIt(\"GetPluginChain should return the plugin chain\", func() {\n\t\t\tExpect(c.GetPluginChain()).To(Equal(pluginChain))\n\t\t})\n\n\t\tIt(\"SetPluginChain should set the plugin chain\", func() {\n\t\t\tExpect(c.SetPluginChain(otherPluginChain)).To(Succeed())\n\t\t\tExpect([]string(c.PluginChain)).To(Equal(otherPluginChain))\n\t\t})\n\t})\n\n\tContext(\"Multi group\", func() {\n\t\tIt(\"IsMultiGroup should return false if not set\", func() {\n\t\t\tExpect(c.IsMultiGroup()).To(BeFalse())\n\t\t})\n\n\t\tIt(\"IsMultiGroup should return true if set\", func() {\n\t\t\tc.MultiGroup = true\n\t\t\tExpect(c.IsMultiGroup()).To(BeTrue())\n\t\t})\n\n\t\tIt(\"SetMultiGroup should enable multi-group support\", func() {\n\t\t\tExpect(c.SetMultiGroup()).To(Succeed())\n\t\t\tExpect(c.MultiGroup).To(BeTrue())\n\t\t})\n\n\t\tIt(\"ClearMultiGroup should disable multi-group support\", func() {\n\t\t\tc.MultiGroup = true\n\t\t\tExpect(c.ClearMultiGroup()).To(Succeed())\n\t\t\tExpect(c.MultiGroup).To(BeFalse())\n\t\t})\n\t})\n\n\tContext(\"Resources\", func() {\n\t\tvar (\n\t\t\tres              resource.Resource\n\t\t\tresWithoutPlural resource.Resource\n\t\t\tcheckResource    func(result, expected resource.Resource)\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tres = resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t},\n\t\t\t\tPlural: \"kinds\",\n\t\t\t\tPath:   \"api/v1\",\n\t\t\t\tAPI: &resource.API{\n\t\t\t\t\tCRDVersion: \"v1\",\n\t\t\t\t\tNamespaced: true,\n\t\t\t\t},\n\t\t\t\tController: true,\n\t\t\t\tWebhooks: &resource.Webhooks{\n\t\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\t\tDefaulting:     true,\n\t\t\t\t\tValidation:     true,\n\t\t\t\t\tConversion:     true,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresWithoutPlural = res.Copy()\n\n\t\t\t// As some of the tests insert directly into the slice without using the interface methods,\n\t\t\t// regular plural forms should not be present in here. rsWithoutPlural is used for this purpose.\n\t\t\tresWithoutPlural.Plural = \"\"\n\n\t\t\t// Auxiliary function for GetResource, AddResource and UpdateResource tests\n\t\t\tcheckResource = func(result, expected resource.Resource) {\n\t\t\t\tExpect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue())\n\t\t\t\tExpect(result.Plural).To(Equal(expected.Plural))\n\t\t\t\tExpect(result.Path).To(Equal(expected.Path))\n\t\t\t\tif expected.API == nil {\n\t\t\t\t\tExpect(result.API).To(BeNil())\n\t\t\t\t} else {\n\t\t\t\t\tExpect(result.API).NotTo(BeNil())\n\t\t\t\t\tExpect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion))\n\t\t\t\t\tExpect(result.API.Namespaced).To(Equal(expected.API.Namespaced))\n\t\t\t\t}\n\t\t\t\tExpect(result.Controller).To(Equal(expected.Controller))\n\t\t\t\tif expected.Webhooks == nil {\n\t\t\t\t\tExpect(result.Webhooks).To(BeNil())\n\t\t\t\t} else {\n\t\t\t\t\tExpect(result.Webhooks).NotTo(BeNil())\n\t\t\t\t\tExpect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion))\n\t\t\t\t\tExpect(result.Webhooks.Defaulting).To(Equal(expected.Webhooks.Defaulting))\n\t\t\t\t\tExpect(result.Webhooks.Validation).To(Equal(expected.Webhooks.Validation))\n\t\t\t\t\tExpect(result.Webhooks.Conversion).To(Equal(expected.Webhooks.Conversion))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tDescribeTable(\"ResourcesLength should return the number of resources\",\n\t\t\tfunc(n int) {\n\t\t\t\tfor range n {\n\t\t\t\t\tc.Resources = append(c.Resources, resWithoutPlural)\n\t\t\t\t}\n\t\t\t\tExpect(c.ResourcesLength()).To(Equal(n))\n\t\t\t},\n\t\t\tEntry(\"for no resources\", 0),\n\t\t\tEntry(\"for one resource\", 1),\n\t\t\tEntry(\"for several resources\", 3),\n\t\t)\n\n\t\tIt(\"HasResource should return false for a non-existent resource\", func() {\n\t\t\tExpect(c.HasResource(res.GVK)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"HasResource should return true for an existent resource\", func() {\n\t\t\tc.Resources = append(c.Resources, resWithoutPlural)\n\t\t\tExpect(c.HasResource(res.GVK)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"GetResource should fail for a non-existent resource\", func() {\n\t\t\t_, err := c.GetResource(res.GVK)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\n\t\tIt(\"GetResource should return an existent resource\", func() {\n\t\t\tc.Resources = append(c.Resources, resWithoutPlural)\n\t\t\tr, err := c.GetResource(res.GVK)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcheckResource(r, res)\n\t\t})\n\n\t\tIt(\"GetResources should return a slice of the tracked resources\", func() {\n\t\t\tc.Resources = append(c.Resources, resWithoutPlural, resWithoutPlural, resWithoutPlural)\n\t\t\tresources, err := c.GetResources()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).To(Equal([]resource.Resource{res, res, res}))\n\t\t})\n\n\t\tIt(\"AddResource should add the provided resource if non-existent\", func() {\n\t\t\tl := len(c.Resources)\n\t\t\tExpect(c.AddResource(res)).To(Succeed())\n\t\t\tExpect(c.Resources).To(HaveLen(l + 1))\n\n\t\t\tcheckResource(c.Resources[0], resWithoutPlural)\n\t\t})\n\n\t\tIt(\"AddResource should do nothing if the resource already exists\", func() {\n\t\t\tc.Resources = append(c.Resources, res)\n\t\t\tl := len(c.Resources)\n\t\t\tExpect(c.AddResource(res)).To(Succeed())\n\t\t\tExpect(c.Resources).To(HaveLen(l))\n\t\t})\n\n\t\tIt(\"UpdateResource should add the provided resource if non-existent\", func() {\n\t\t\tl := len(c.Resources)\n\t\t\tExpect(c.UpdateResource(res)).To(Succeed())\n\t\t\tExpect(c.Resources).To(HaveLen(l + 1))\n\n\t\t\tcheckResource(c.Resources[0], resWithoutPlural)\n\t\t})\n\n\t\tIt(\"UpdateResource should update it if the resource already exists\", func() {\n\t\t\tr := resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t},\n\t\t\t\tPath: \"api/v1\",\n\t\t\t}\n\t\t\tc.Resources = append(c.Resources, r)\n\t\t\tl := len(c.Resources)\n\t\t\tcheckResource(c.Resources[0], r)\n\n\t\t\tExpect(c.UpdateResource(res)).To(Succeed())\n\t\t\tExpect(c.Resources).To(HaveLen(l))\n\n\t\t\tcheckResource(c.Resources[0], resWithoutPlural)\n\t\t})\n\n\t\tIt(\"HasGroup should return false with no tracked resources\", func() {\n\t\t\tExpect(c.HasGroup(res.Group)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"HasGroup should return true with tracked resources in the same group\", func() {\n\t\t\tc.Resources = append(c.Resources, res)\n\t\t\tExpect(c.HasGroup(res.Group)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"HasGroup should return false with tracked resources in other group\", func() {\n\t\t\tc.Resources = append(c.Resources, res)\n\t\t\tExpect(c.HasGroup(\"other-group\")).To(BeFalse())\n\t\t})\n\n\t\tIt(\"ListCRDVersions should return an empty list with no tracked resources\", func() {\n\t\t\tExpect(c.ListCRDVersions()).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"ListCRDVersions should return a list of tracked resources CRD versions\", func() {\n\t\t\tc.Resources = append(c.Resources,\n\t\t\t\tresource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   res.Group,\n\t\t\t\t\t\tVersion: res.Version,\n\t\t\t\t\t\tKind:    res.Kind,\n\t\t\t\t\t},\n\t\t\t\t\tAPI: &resource.API{CRDVersion: \"v1beta1\"},\n\t\t\t\t},\n\t\t\t\tresource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   res.Group,\n\t\t\t\t\t\tVersion: res.Version,\n\t\t\t\t\t\tKind:    \"OtherKind\",\n\t\t\t\t\t},\n\t\t\t\t\tAPI: &resource.API{CRDVersion: \"v1\"},\n\t\t\t\t},\n\t\t\t)\n\t\t\tversions := c.ListCRDVersions()\n\t\t\tslices.Sort(versions) // ListCRDVersions has no order guarantee so sorting for reproducibility\n\t\t\tExpect(versions).To(Equal([]string{\"v1\", \"v1beta1\"}))\n\t\t})\n\n\t\tIt(\"ListWebhookVersions should return an empty list with no tracked resources\", func() {\n\t\t\tExpect(c.ListWebhookVersions()).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"ListWebhookVersions should return a list of tracked resources webhook versions\", func() {\n\t\t\tc.Resources = append(c.Resources,\n\t\t\t\tresource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   res.Group,\n\t\t\t\t\t\tVersion: res.Version,\n\t\t\t\t\t\tKind:    res.Kind,\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: &resource.Webhooks{WebhookVersion: \"v1beta1\"},\n\t\t\t\t},\n\t\t\t\tresource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   res.Group,\n\t\t\t\t\t\tVersion: res.Version,\n\t\t\t\t\t\tKind:    \"OtherKind\",\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: &resource.Webhooks{WebhookVersion: \"v1\"},\n\t\t\t\t},\n\t\t\t)\n\t\t\tversions := c.ListWebhookVersions()\n\t\t\tslices.Sort(versions) // ListWebhookVersions has no order guarantee so sorting for reproducibility\n\t\t\tExpect(versions).To(Equal([]string{\"v1\", \"v1beta1\"}))\n\t\t})\n\t})\n\n\tContext(\"Plugins\", func() {\n\t\t// Test plugin config. Don't want to export this config, but need it to\n\t\t// be accessible by test.\n\t\ttype PluginConfig struct {\n\t\t\tData1 string `json:\"data-1\"`\n\t\t\tData2 string `json:\"data-2,omitempty\"`\n\t\t}\n\n\t\tconst (\n\t\t\tkey = \"plugin-x\"\n\t\t)\n\n\t\tvar (\n\t\t\tc0, c1, c2 Cfg\n\t\t\tpluginCfg  PluginConfig\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tc0 = Cfg{\n\t\t\t\tVersion:     Version,\n\t\t\t\tDomain:      domain,\n\t\t\t\tRepository:  repo,\n\t\t\t\tName:        name,\n\t\t\t\tPluginChain: pluginChain,\n\t\t\t}\n\t\t\tc1 = Cfg{\n\t\t\t\tVersion:     Version,\n\t\t\t\tDomain:      domain,\n\t\t\t\tRepository:  repo,\n\t\t\t\tName:        name,\n\t\t\t\tPluginChain: pluginChain,\n\t\t\t\tPlugins: pluginConfigs{\n\t\t\t\t\tkey: map[string]any{\n\t\t\t\t\t\t\"data-1\": \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tc2 = Cfg{\n\t\t\t\tVersion:     Version,\n\t\t\t\tDomain:      domain,\n\t\t\t\tRepository:  repo,\n\t\t\t\tName:        name,\n\t\t\t\tPluginChain: pluginChain,\n\t\t\t\tPlugins: pluginConfigs{\n\t\t\t\t\tkey: map[string]any{\n\t\t\t\t\t\t\"data-1\": \"plugin value 1\",\n\t\t\t\t\t\t\"data-2\": \"plugin value 2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tpluginCfg = PluginConfig{\n\t\t\t\tData1: \"plugin value 1\",\n\t\t\t\tData2: \"plugin value 2\",\n\t\t\t}\n\t\t})\n\n\t\tIt(\"DecodePluginConfig should fail for no plugin config object\", func() {\n\t\t\terr := c0.DecodePluginConfig(key, &pluginCfg)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(config.PluginKeyNotFoundError{Key: key}))\n\t\t})\n\n\t\tIt(\"DecodePluginConfig should fail to retrieve data from a non-existent plugin\", func() {\n\t\t\terr := c1.DecodePluginConfig(\"plugin-y\", &pluginCfg)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err).To(MatchError(config.PluginKeyNotFoundError{Key: \"plugin-y\"}))\n\t\t})\n\n\t\tDescribeTable(\"DecodePluginConfig should retrieve the plugin data correctly\",\n\t\t\tfunc(getCfg func() Cfg, expected func() PluginConfig) {\n\t\t\t\tpluginCfg = PluginConfig{} // reset to not reuse values\n\t\t\t\tExpect(getCfg().DecodePluginConfig(key, &pluginCfg)).To(Succeed())\n\t\t\t\tExpect(pluginCfg).To(Equal(expected()))\n\t\t\t},\n\t\t\tEntry(\"for an empty plugin config object\", func() Cfg { return c1 }, func() PluginConfig { return PluginConfig{} }),\n\t\t\tEntry(\"for a full plugin config object\", func() Cfg { return c2 }, func() PluginConfig { return pluginCfg }),\n\t\t\t// TODO (coverage): add cases where yaml.Marshal returns an error\n\t\t\t// TODO (coverage): add cases where yaml.Unmarshal returns an error\n\t\t)\n\n\t\tDescribeTable(\"EncodePluginConfig should encode the plugin data correctly\",\n\t\t\tfunc(getPluginCfg func() PluginConfig, expectedCfg func() Cfg) {\n\t\t\t\tExpect(c.EncodePluginConfig(key, getPluginCfg())).To(Succeed())\n\t\t\t\tExpect(c).To(Equal(expectedCfg()))\n\t\t\t},\n\t\t\tEntry(\"for an empty plugin config object\", func() PluginConfig { return PluginConfig{} }, func() Cfg { return c1 }),\n\t\t\tEntry(\"for a full plugin config object\", func() PluginConfig { return pluginCfg }, func() Cfg { return c2 }),\n\t\t\t// TODO (coverage): add cases where yaml.Marshal returns an error\n\t\t\t// TODO (coverage): add cases where yaml.Unmarshal returns an error\n\t\t)\n\t})\n\n\tContext(\"Persistence\", func() {\n\t\tvar (\n\t\t\tc1, c2        Cfg\n\t\t\ts1, s1bis, s2 string\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tc1 = Cfg{\n\t\t\t\tVersion:     Version,\n\t\t\t\tDomain:      domain,\n\t\t\t\tRepository:  repo,\n\t\t\t\tName:        name,\n\t\t\t\tPluginChain: pluginChain,\n\t\t\t}\n\t\t\tc2 = Cfg{\n\t\t\t\tVersion:     Version,\n\t\t\t\tDomain:      otherDomain,\n\t\t\t\tRepository:  otherRepo,\n\t\t\t\tName:        otherName,\n\t\t\t\tPluginChain: otherPluginChain,\n\t\t\t\tMultiGroup:  true,\n\t\t\t\tResources: []resource.Resource{\n\t\t\t\t\t{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\t\t\tKind:    \"Kind2\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAPI:        &resource.API{CRDVersion: \"v1\"},\n\t\t\t\t\t\tController: true,\n\t\t\t\t\t\tWebhooks:   &resource.Webhooks{WebhookVersion: \"v1\"},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\t\t\tVersion: \"v1-beta\",\n\t\t\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPlural:   \"kindes\",\n\t\t\t\t\t\tAPI:      nil,\n\t\t\t\t\t\tWebhooks: nil,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   \"group2\",\n\t\t\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAPI: &resource.API{\n\t\t\t\t\t\t\tCRDVersion: \"v1\",\n\t\t\t\t\t\t\tNamespaced: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tController: true,\n\t\t\t\t\t\tWebhooks: &resource.Webhooks{\n\t\t\t\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\t\t\t\tDefaulting:     true,\n\t\t\t\t\t\t\tValidation:     true,\n\t\t\t\t\t\t\tConversion:     true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tPlugins: pluginConfigs{\n\t\t\t\t\t\"plugin-x\": map[string]any{\n\t\t\t\t\t\t\"data-1\": \"single plugin datum\",\n\t\t\t\t\t},\n\t\t\t\t\t\"plugin-y/v1\": map[string]any{\n\t\t\t\t\t\t\"data-1\": \"plugin value 1\",\n\t\t\t\t\t\t\"data-2\": \"plugin value 2\",\n\t\t\t\t\t\t\"data-3\": []string{\"plugin value 3\", \"plugin value 4\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\t// TODO: include cases with Path when added\n\t\t\ts1 = `domain: my.domain\nlayout:\n- go.kubebuilder.io/v2\nprojectName: ProjectName\nrepo: myrepo\nversion: \"3\"\n`\n\t\t\ts1bis = `domain: my.domain\nlayout: go.kubebuilder.io/v2\nprojectName: ProjectName\nrepo: myrepo\nversion: \"3\"\n`\n\t\t\ts2 = `domain: other.domain\nlayout:\n- go.kubebuilder.io/v3\nmultigroup: true\nplugins:\n  plugin-x:\n    data-1: single plugin datum\n  plugin-y/v1:\n    data-1: plugin value 1\n    data-2: plugin value 2\n    data-3:\n    - plugin value 3\n    - plugin value 4\nprojectName: OtherProjectName\nrepo: otherrepo\nresources:\n- group: group\n  kind: Kind\n  version: v1\n- api:\n    crdVersion: v1\n  controller: true\n  group: group\n  kind: Kind2\n  version: v1\n  webhooks:\n    webhookVersion: v1\n- group: group\n  kind: Kind\n  plural: kindes\n  version: v1-beta\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  group: group2\n  kind: Kind\n  version: v1\n  webhooks:\n    conversion: true\n    defaulting: true\n    validation: true\n    webhookVersion: v1\nversion: \"3\"\n`\n\t\t})\n\n\t\tDescribeTable(\"MarshalYAML should succeed\",\n\t\t\tfunc(getCfg func() Cfg, getContent func() string) {\n\t\t\t\tb, err := getCfg().MarshalYAML()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(getContent()))\n\t\t\t},\n\t\t\tEntry(\"for a basic configuration\", func() Cfg { return c1 }, func() string { return s1 }),\n\t\t\tEntry(\"for a full configuration\", func() Cfg { return c2 }, func() string { return s2 }),\n\t\t)\n\n\t\tDescribeTable(\"UnmarshalYAML should succeed\",\n\t\t\tfunc(getContent func() string, getCfg func() Cfg) {\n\t\t\t\tvar unmarshalled Cfg\n\t\t\t\tExpect(unmarshalled.UnmarshalYAML([]byte(getContent()))).To(Succeed())\n\t\t\t\tc := getCfg()\n\t\t\t\tExpect(unmarshalled.Version.Compare(c.Version)).To(Equal(0))\n\t\t\t\tExpect(unmarshalled.Domain).To(Equal(c.Domain))\n\t\t\t\tExpect(unmarshalled.Repository).To(Equal(c.Repository))\n\t\t\t\tExpect(unmarshalled.Name).To(Equal(c.Name))\n\t\t\t\tExpect(unmarshalled.PluginChain).To(Equal(c.PluginChain))\n\t\t\t\tExpect(unmarshalled.MultiGroup).To(Equal(c.MultiGroup))\n\t\t\t\tExpect(unmarshalled.Resources).To(Equal(c.Resources))\n\t\t\t\tExpect(unmarshalled.Plugins).To(HaveLen(len(c.Plugins)))\n\t\t\t\t// TODO: fully test Plugins field and not on its length\n\t\t\t},\n\t\t\tEntry(\"basic\", func() string { return s1 }, func() Cfg { return c1 }),\n\t\t\tEntry(\"full\", func() string { return s2 }, func() Cfg { return c2 }),\n\t\t\tEntry(\"string layout\", func() string { return s1bis }, func() Cfg { return c1 }),\n\t\t)\n\n\t\tDescribeTable(\"UnmarshalYAML should fail\",\n\t\t\tfunc(content string) {\n\t\t\t\tvar c Cfg\n\t\t\t\tExpect(c.UnmarshalYAML([]byte(content))).NotTo(Succeed())\n\t\t\t},\n\t\t\tEntry(\"for invalid YAML\", `invalid: yaml: content\n  badly indented`),\n\t\t)\n\n\t\t// Test forward compatibility - unknown fields should be ignored\n\t\tContext(\"Forward compatibility\", func() {\n\t\t\tIt(\"should ignore unknown fields for forward compatibility\", func() {\n\t\t\t\t// Old kubebuilder reading PROJECT file with new fields\n\t\t\t\tcontent := `version: \"3\"\ndomain: example.com\nrepo: github.com/example/project\nprojectName: test-project\nunknownField: \"should be ignored\"\nanotherUnknownField: 123`\n\n\t\t\t\tvar c Cfg\n\t\t\t\tExpect(c.UnmarshalYAML([]byte(content))).To(Succeed())\n\t\t\t\tExpect(c.Domain).To(Equal(\"example.com\"))\n\t\t\t\tExpect(c.Repository).To(Equal(\"github.com/example/project\"))\n\t\t\t\tExpect(c.Name).To(Equal(\"test-project\"))\n\t\t\t})\n\n\t\t\tIt(\"should ignore the new 'controllers' field for backward compatibility\", func() {\n\t\t\t\t// Simulates old kubebuilder reading PROJECT file from new kubebuilder\n\t\t\t\t// that has the \"controllers\" field in resources\n\t\t\t\tcontent := `version: \"3\"\ndomain: testproject.org\nrepo: sigs.k8s.io/kubebuilder/testdata/project\nprojectName: test-project\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controllers:\n  - name: captain\n  - name: captain-backup\n  domain: testproject.org\n  group: crew\n  kind: Captain\n  version: v1`\n\n\t\t\t\tvar c Cfg\n\t\t\t\tExpect(c.UnmarshalYAML([]byte(content))).To(Succeed())\n\t\t\t\tExpect(c.Domain).To(Equal(\"testproject.org\"))\n\t\t\t\tExpect(c.Resources).To(HaveLen(1))\n\t\t\t\tExpect(c.Resources[0].Group).To(Equal(\"crew\"))\n\t\t\t\tExpect(c.Resources[0].Kind).To(Equal(\"Captain\"))\n\t\t\t\t// The \"controllers\" field should be silently ignored by older versions\n\t\t\t})\n\n\t\t\tIt(\"should handle mixed known and unknown fields\", func() {\n\t\t\t\tcontent := `version: \"3\"\ndomain: example.com\nrepo: github.com/example/project\nprojectName: test-project\nlayout:\n- go.kubebuilder.io/v4\nunknownField1: \"ignored\"\nmultigroup: true\nunknownField2:\n  nested: \"also ignored\"\nresources:\n- group: apps\n  version: v1\n  kind: Deployment\n  newField: \"ignored in resource\"`\n\n\t\t\t\tvar c Cfg\n\t\t\t\tExpect(c.UnmarshalYAML([]byte(content))).To(Succeed())\n\t\t\t\tExpect(c.Domain).To(Equal(\"example.com\"))\n\t\t\t\tExpect(c.MultiGroup).To(BeTrue())\n\t\t\t\tExpect(c.PluginChain).To(Equal(stringSlice{\"go.kubebuilder.io/v4\"}))\n\t\t\t\tExpect(c.Resources).To(HaveLen(1))\n\t\t\t})\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"New\", func() {\n\tIt(\"should return a new config for project configuration 3\", func() {\n\t\tExpect(New().GetVersion().Compare(Version)).To(Equal(0))\n\t})\n})\n"
  },
  {
    "path": "pkg/config/version.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nvar (\n\terrNonPositive = errors.New(\"project version number must be positive\")\n\terrEmpty       = errors.New(\"project version is empty\")\n)\n\n// Version is a project version containing a non-zero positive integer and a stage value that represents stability.\ntype Version struct {\n\t// Number denotes the current version of a plugin. Two different numbers between versions\n\t// indicate that they are incompatible.\n\tNumber int\n\t// Stage indicates stability.\n\tStage stage.Stage\n}\n\n// Parse parses version inline, assuming it adheres to format: [1-9][0-9]*(-(alpha|beta))?\nfunc (v *Version) Parse(version string) error {\n\tif len(version) == 0 {\n\t\treturn errEmpty\n\t}\n\n\tsubstrings := strings.SplitN(version, \"-\", 2)\n\n\tvar err error\n\tif v.Number, err = strconv.Atoi(substrings[0]); err != nil {\n\t\t// Let's check if the `-` belonged to a negative number\n\t\tif n, errParse := strconv.Atoi(version); errParse == nil && n < 0 {\n\t\t\treturn errNonPositive\n\t\t}\n\t\treturn fmt.Errorf(\"failed to convert version number %q: %w\", substrings[0], err)\n\t} else if v.Number == 0 {\n\t\treturn errNonPositive\n\t}\n\n\tif len(substrings) > 1 {\n\t\tif err = v.Stage.Parse(substrings[1]); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse stage: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// String returns the string representation of v.\nfunc (v Version) String() string {\n\tstageStr := v.Stage.String()\n\tif len(stageStr) == 0 {\n\t\treturn fmt.Sprintf(\"%d\", v.Number)\n\t}\n\treturn fmt.Sprintf(\"%d-%s\", v.Number, stageStr)\n}\n\n// Validate ensures that the version number is positive and the stage is one of the valid stages.\nfunc (v Version) Validate() error {\n\tif v.Number < 1 {\n\t\treturn errNonPositive\n\t}\n\n\tif err := v.Stage.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"failed to validate stage: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Compare returns -1 if v < other, 0 if v == other, and 1 if v > other.\nfunc (v Version) Compare(other Version) int {\n\tif v.Number > other.Number {\n\t\treturn 1\n\t} else if v.Number < other.Number {\n\t\treturn -1\n\t}\n\n\treturn v.Stage.Compare(other.Stage)\n}\n\n// IsStable returns true if v is stable.\nfunc (v Version) IsStable() bool {\n\treturn v.Stage.IsStable()\n}\n\n// MarshalJSON implements json.Marshaller\nfunc (v Version) MarshalJSON() ([]byte, error) {\n\tif err := v.Validate(); err != nil {\n\t\treturn []byte{}, fmt.Errorf(\"failed to validate version: %w\", err)\n\t}\n\n\tmarshaled, err := json.Marshal(v.String())\n\tif err != nil {\n\t\treturn []byte{}, fmt.Errorf(\"failed to marshal version: %w\", err)\n\t}\n\n\treturn marshaled, nil\n}\n\n// UnmarshalJSON implements json.Unmarshaller\nfunc (v *Version) UnmarshalJSON(b []byte) error {\n\tvar str string\n\tif err := json.Unmarshal(b, &str); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal version: %w\", err)\n\t}\n\n\treturn v.Parse(str)\n}\n"
  },
  {
    "path": "pkg/config/version_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage config\n\nimport (\n\t\"slices\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nvar _ = Describe(\"Version\", func() {\n\t// Parse, String and Validate are tested by MarshalJSON and UnmarshalJSON\n\n\tContext(\"Compare\", func() {\n\t\tvar (\n\t\t\tversions       []Version\n\t\t\tsortedVersions []Version\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\t// Test Compare() by sorting a list.\n\t\t\tversions = []Version{\n\t\t\t\t{Number: 2, Stage: stage.Alpha},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t\t{Number: 4, Stage: stage.Beta},\n\t\t\t\t{Number: 1, Stage: stage.Alpha},\n\t\t\t\t{Number: 4},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 30},\n\t\t\t\t{Number: 4, Stage: stage.Alpha},\n\t\t\t}\n\n\t\t\tsortedVersions = []Version{\n\t\t\t\t{Number: 1, Stage: stage.Alpha},\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2, Stage: stage.Alpha},\n\t\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t\t{Number: 4, Stage: stage.Alpha},\n\t\t\t\t{Number: 4, Stage: stage.Beta},\n\t\t\t\t{Number: 4},\n\t\t\t\t{Number: 30},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"sorts a valid list of versions correctly\", func() {\n\t\t\tslices.SortStableFunc(versions, func(a, b Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(sortedVersions))\n\t\t})\n\t})\n\n\tContext(\"IsStable\", func() {\n\t\tDescribeTable(\"should return true for stable versions\",\n\t\t\tfunc(version Version) { Expect(version.IsStable()).To(BeTrue()) },\n\t\t\tEntry(\"for version 1\", Version{Number: 1}),\n\t\t\tEntry(\"for version 1 (stable)\", Version{Number: 1, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 22\", Version{Number: 22}),\n\t\t\tEntry(\"for version 22 (stable)\", Version{Number: 22, Stage: stage.Stable}),\n\t\t)\n\n\t\tDescribeTable(\"should return false for unstable versions\",\n\t\t\tfunc(version Version) { Expect(version.IsStable()).To(BeFalse()) },\n\t\t\tEntry(\"for version 1 (alpha)\", Version{Number: 1, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 1 (beta)\", Version{Number: 1, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 22 (alpha)\", Version{Number: 22, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 22 (beta)\", Version{Number: 22, Stage: stage.Beta}),\n\t\t)\n\t})\n\n\tContext(\"MarshalJSON\", func() {\n\t\tDescribeTable(\"should be marshalled appropriately\",\n\t\t\tfunc(version Version, str string) {\n\t\t\t\tb, err := version.MarshalJSON()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(str))\n\t\t\t},\n\t\t\tEntry(\"for version 1\", Version{Number: 1}, `\"1\"`),\n\t\t\tEntry(\"for version 1 (stable)\", Version{Number: 1, Stage: stage.Stable}, `\"1\"`),\n\t\t\tEntry(\"for version 1 (alpha)\", Version{Number: 1, Stage: stage.Alpha}, `\"1-alpha\"`),\n\t\t\tEntry(\"for version 1 (beta)\", Version{Number: 1, Stage: stage.Beta}, `\"1-beta\"`),\n\t\t\tEntry(\"for version 22\", Version{Number: 22}, `\"22\"`),\n\t\t\tEntry(\"for version 22 (stable)\", Version{Number: 22, Stage: stage.Stable}, `\"22\"`),\n\t\t\tEntry(\"for version 22 (alpha)\", Version{Number: 22, Stage: stage.Alpha}, `\"22-alpha\"`),\n\t\t\tEntry(\"for version 22 (beta)\", Version{Number: 22, Stage: stage.Beta}, `\"22-beta\"`),\n\t\t)\n\n\t\tDescribeTable(\"should fail to be marshalled\",\n\t\t\tfunc(version Version) {\n\t\t\t\t_, err := version.MarshalJSON()\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t},\n\t\t\tEntry(\"for version 0\", Version{Number: 0}),\n\t\t\tEntry(\"for version 0 (stable)\", Version{Number: 0, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha)\", Version{Number: 0, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta)\", Version{Number: 0, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 0 (implicit)\", Version{}),\n\t\t\tEntry(\"for version 0 (stable) (implicit)\", Version{Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha) (implicit)\", Version{Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta) (implicit)\", Version{Stage: stage.Beta}),\n\t\t\tEntry(\"for version -1\", Version{Number: -1}),\n\t\t\tEntry(\"for version -1 (stable)\", Version{Number: -1, Stage: stage.Stable}),\n\t\t\tEntry(\"for version -1 (alpha)\", Version{Number: -1, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version -1 (beta)\", Version{Number: -1, Stage: stage.Beta}),\n\t\t\tEntry(\"for invalid stage\", Version{Stage: stage.Stage(34)}),\n\t\t)\n\t})\n\n\tContext(\"UnmarshalJSON\", func() {\n\t\tDescribeTable(\"should be unmarshalled appropriately\",\n\t\t\tfunc(str string, number int, s stage.Stage) {\n\t\t\t\tvar v Version\n\t\t\t\terr := v.UnmarshalJSON([]byte(str))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(v.Number).To(Equal(number))\n\t\t\t\tExpect(v.Stage).To(Equal(s))\n\t\t\t},\n\t\t\tEntry(\"for version string `1`\", `\"1\"`, 1, stage.Stable),\n\t\t\tEntry(\"for version string `1-alpha`\", `\"1-alpha\"`, 1, stage.Alpha),\n\t\t\tEntry(\"for version string `1-beta`\", `\"1-beta\"`, 1, stage.Beta),\n\t\t\tEntry(\"for version string `22`\", `\"22\"`, 22, stage.Stable),\n\t\t\tEntry(\"for version string `22-alpha`\", `\"22-alpha\"`, 22, stage.Alpha),\n\t\t\tEntry(\"for version string `22-beta`\", `\"22-beta\"`, 22, stage.Beta),\n\t\t)\n\n\t\tDescribeTable(\"should fail to be unmarshalled\",\n\t\t\tfunc(str string) {\n\t\t\t\tvar v Version\n\t\t\t\terr := v.UnmarshalJSON([]byte(str))\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t},\n\t\t\tEntry(\"for empty version string\", ``),\n\t\t\tEntry(\"for version string ``\", `\"\"`),\n\t\t\tEntry(\"for version string `0`\", `\"0\"`),\n\t\t\tEntry(\"for version string `0-alpha`\", `\"0-alpha\"`),\n\t\t\tEntry(\"for version string `0-beta`\", `\"0-beta\"`),\n\t\t\tEntry(\"for version string `-1`\", `\"-1\"`),\n\t\t\tEntry(\"for version string `-1-alpha`\", `\"-1-alpha\"`),\n\t\t\tEntry(\"for version string `-1-beta`\", `\"-1-beta\"`),\n\t\t\tEntry(\"for version string `v1`\", `\"v1\"`),\n\t\t\tEntry(\"for version string `v1-alpha`\", `\"v1-alpha\"`),\n\t\t\tEntry(\"for version string `v1-beta`\", `\"v1-beta\"`),\n\t\t\tEntry(\"for version string `1.0`\", `\"1.0\"`),\n\t\t\tEntry(\"for version string `v1.0`\", `\"v1.0\"`),\n\t\t\tEntry(\"for version string `v1.0-alpha`\", `\"v1.0-alpha\"`),\n\t\t\tEntry(\"for version string `1.0.0`\", `\"1.0.0\"`),\n\t\t\tEntry(\"for version string `1-a`\", `\"1-a\"`),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/errors.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"fmt\"\n)\n\n// This file contains the errors returned by the scaffolding machinery\n// They are exported to be able to check which kind of error was returned\n\n// ValidateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate\ntype ValidateError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e ValidateError) Unwrap() error {\n\treturn e.error\n}\n\n// SetTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults\ntype SetTemplateDefaultsError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e SetTemplateDefaultsError) Unwrap() error {\n\treturn e.error\n}\n\n// ExistsFileError is a wrapper error that will be used for errors when checking for a file existence\ntype ExistsFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e ExistsFileError) Unwrap() error {\n\treturn e.error\n}\n\n// OpenFileError is a wrapper error that will be used for errors when opening a file\ntype OpenFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e OpenFileError) Unwrap() error {\n\treturn e.error\n}\n\n// CreateDirectoryError is a wrapper error that will be used for errors when creating a directory\ntype CreateDirectoryError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e CreateDirectoryError) Unwrap() error {\n\treturn e.error\n}\n\n// CreateFileError is a wrapper error that will be used for errors when creating a file\ntype CreateFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e CreateFileError) Unwrap() error {\n\treturn e.error\n}\n\n// ReadFileError is a wrapper error that will be used for errors when reading a file\ntype ReadFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e ReadFileError) Unwrap() error {\n\treturn e.error\n}\n\n// WriteFileError is a wrapper error that will be used for errors when writing a file\ntype WriteFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e WriteFileError) Unwrap() error {\n\treturn e.error\n}\n\n// CloseFileError is a wrapper error that will be used for errors when closing a file\ntype CloseFileError struct {\n\terror\n}\n\n// Unwrap implements Wrapper interface\nfunc (e CloseFileError) Unwrap() error {\n\treturn e.error\n}\n\n// ModelAlreadyExistsError is returned if the file is expected not to exist but a previous model does\ntype ModelAlreadyExistsError struct {\n\tpath string\n}\n\n// Error implements error interface\nfunc (e ModelAlreadyExistsError) Error() string {\n\treturn fmt.Sprintf(\"failed to create %s: model already exists\", e.path)\n}\n\n// UnknownIfExistsActionError is returned if the if-exists-action is unknown\ntype UnknownIfExistsActionError struct {\n\tpath           string\n\tifExistsAction IfExistsAction\n}\n\n// Error implements error interface\nfunc (e UnknownIfExistsActionError) Error() string {\n\treturn fmt.Sprintf(\"unknown behavior if file exists (%d) for %s\", e.ifExistsAction, e.path)\n}\n\n// FileAlreadyExistsError is returned if the file is expected not to exist but it does\ntype FileAlreadyExistsError struct {\n\tpath string\n}\n\n// Error implements error interface\nfunc (e FileAlreadyExistsError) Error() string {\n\treturn fmt.Sprintf(\"failed to create %s: file already exists\", e.path)\n}\n"
  },
  {
    "path": "pkg/machinery/errors_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"errors\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Errors\", func() {\n\tvar (\n\t\tpath    string\n\t\ttestErr error\n\t)\n\n\tBeforeEach(func() {\n\t\tpath = filepath.Join(\"path\", \"to\", \"file\")\n\t\ttestErr = errors.New(\"test error\")\n\t})\n\n\tDescribeTable(\"should contain the wrapped error\",\n\t\tfunc(getErr func() error) {\n\t\t\terr := getErr()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(errors.Is(err, testErr)).To(BeTrue())\n\t\t},\n\t\tEntry(\"for validate errors\", func() error { return ValidateError{testErr} }),\n\t\tEntry(\"for set template defaults errors\", func() error { return SetTemplateDefaultsError{testErr} }),\n\t\tEntry(\"for file existence errors\", func() error { return ExistsFileError{testErr} }),\n\t\tEntry(\"for file opening errors\", func() error { return OpenFileError{testErr} }),\n\t\tEntry(\"for directory creation errors\", func() error { return CreateDirectoryError{testErr} }),\n\t\tEntry(\"for file creation errors\", func() error { return CreateFileError{testErr} }),\n\t\tEntry(\"for file reading errors\", func() error { return ReadFileError{testErr} }),\n\t\tEntry(\"for file writing errors\", func() error { return WriteFileError{testErr} }),\n\t\tEntry(\"for file closing errors\", func() error { return CloseFileError{testErr} }),\n\t)\n\n\t// NOTE: the following test increases coverage\n\tIt(\"should print a descriptive error message\", func() {\n\t\tExpect(ModelAlreadyExistsError{path}.Error()).To(ContainSubstring(\"model already exists\"))\n\t\tExpect(UnknownIfExistsActionError{path, -1}.Error()).To(ContainSubstring(\"unknown behavior if file exists\"))\n\t\tExpect(FileAlreadyExistsError{path}.Error()).To(ContainSubstring(\"file already exists\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/file.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\n// IfExistsAction determines what to do if the scaffold file already exists\ntype IfExistsAction int\n\nconst (\n\t// SkipFile skips the file and moves to the next one\n\tSkipFile IfExistsAction = iota\n\n\t// Error returns an error and stops processing\n\tError\n\n\t// OverwriteFile truncates and overwrites the existing file\n\tOverwriteFile\n)\n\n// IfNotExistsAction determines what to do if a file to be updated does not exist\ntype IfNotExistsAction int\n\nconst (\n\t// ErrorIfNotExist returns an error and stops processing (default behavior)\n\tErrorIfNotExist IfNotExistsAction = iota\n\n\t// IgnoreFile skips the file and logs a message if it does not exist\n\tIgnoreFile\n)\n\n// File describes a file that will be written\ntype File struct {\n\t// Path is the file to write\n\tPath string\n\n\t// Contents is the generated output\n\tContents string\n\n\t// IfExistsAction determines what to do if the file exists\n\tIfExistsAction IfExistsAction\n\n\t// IfNotExistsAction determines what to do if the file is missing (optional updates only)\n\tIfNotExistsAction IfNotExistsAction\n}\n"
  },
  {
    "path": "pkg/machinery/filesystem.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"github.com/spf13/afero\"\n)\n\n// Filesystem abstracts the underlying disk for scaffolding\ntype Filesystem struct {\n\tFS afero.Fs\n}\n"
  },
  {
    "path": "pkg/machinery/funcmap.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"golang.org/x/text/cases\"\n)\n\n// DefaultFuncMap returns the default template.FuncMap for rendering the template.\nfunc DefaultFuncMap() template.FuncMap {\n\treturn template.FuncMap{\n\t\t\"title\":      cases.Title,\n\t\t\"lower\":      strings.ToLower,\n\t\t\"upper\":      strings.ToUpper,\n\t\t\"isEmptyStr\": isEmptyString,\n\t\t\"hashFNV\":    hashFNV,\n\t}\n}\n\n// isEmptyString returns whether the string is empty\nfunc isEmptyString(s string) bool {\n\treturn s == \"\"\n}\n\n// hashFNV will generate a random string useful for generating a unique string\nfunc hashFNV(s string) string {\n\thasher := fnv.New32a()\n\t// Hash.Write never returns an error\n\t_, _ = hasher.Write([]byte(s))\n\treturn fmt.Sprintf(\"%x\", hasher.Sum(nil))\n}\n"
  },
  {
    "path": "pkg/machinery/funcmap_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"funcmap functions\", func() {\n\tContext(\"isEmptyString\", func() {\n\t\tIt(\"should return true for empty strings\", func() {\n\t\t\tExpect(isEmptyString(\"\")).To(BeTrue())\n\t\t})\n\n\t\tDescribeTable(\"should return false for any other string\",\n\t\t\tfunc(str string) { Expect(isEmptyString(str)).To(BeFalse()) },\n\t\t\tEntry(`for \"a\"`, \"a\"),\n\t\t\tEntry(`for \"1\"`, \"1\"),\n\t\t\tEntry(`for \"-\"`, \"-\"),\n\t\t\tEntry(`for \".\"`, \".\"),\n\t\t)\n\t})\n\n\tContext(\"hashFNV\", func() {\n\t\tIt(\"should hash the input\", func() {\n\t\t\tExpect(hashFNV(\"test\")).To(Equal(\"afd071e5\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/injector.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// injector is used to inject certain fields to file templates.\ntype injector struct {\n\t// config stores the project configuration.\n\tconfig config.Config\n\n\t// boilerplate is the copyright comment added at the top of scaffolded files.\n\tboilerplate string\n\n\t// resource contains the information of the API that is being scaffolded.\n\tresource *resource.Resource\n}\n\n// injectInto injects fields from the universe into the builder\nfunc (i injector) injectInto(builder Builder) {\n\t// Inject project configuration\n\tif i.config != nil {\n\t\tif builderWithDomain, hasDomain := builder.(HasDomain); hasDomain {\n\t\t\tbuilderWithDomain.InjectDomain(i.config.GetDomain())\n\t\t}\n\t\tif builderWithRepository, hasRepository := builder.(HasRepository); hasRepository {\n\t\t\tbuilderWithRepository.InjectRepository(i.config.GetRepository())\n\t\t}\n\t\tif builderWithProjectName, hasProjectName := builder.(HasProjectName); hasProjectName {\n\t\t\tbuilderWithProjectName.InjectProjectName(i.config.GetProjectName())\n\t\t}\n\t\tif builderWithMultiGroup, hasMultiGroup := builder.(HasMultiGroup); hasMultiGroup {\n\t\t\tbuilderWithMultiGroup.InjectMultiGroup(i.config.IsMultiGroup())\n\t\t}\n\t\tif builderWithNamespaced, hasNamespaced := builder.(HasNamespaced); hasNamespaced {\n\t\t\tbuilderWithNamespaced.InjectNamespaced(i.config.IsNamespaced())\n\t\t}\n\t}\n\t// Inject boilerplate\n\tif builderWithBoilerplate, hasBoilerplate := builder.(HasBoilerplate); hasBoilerplate {\n\t\tbuilderWithBoilerplate.InjectBoilerplate(i.boilerplate)\n\t}\n\t// Inject resource\n\tif i.resource != nil {\n\t\tif builderWithResource, hasResource := builder.(HasResource); hasResource {\n\t\t\tbuilderWithResource.InjectResource(i.resource)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/machinery/injector_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\ntype templateBase struct {\n\tpath           string\n\tifExistsAction IfExistsAction\n}\n\nfunc (t templateBase) GetPath() string {\n\treturn t.path\n}\n\nfunc (t templateBase) GetIfExistsAction() IfExistsAction {\n\treturn t.ifExistsAction\n}\n\ntype templateWithDomain struct {\n\ttemplateBase\n\tdomain string\n}\n\nfunc (t *templateWithDomain) InjectDomain(domain string) {\n\tt.domain = domain\n}\n\ntype templateWithRepository struct {\n\ttemplateBase\n\trepository string\n}\n\nfunc (t *templateWithRepository) InjectRepository(repository string) {\n\tt.repository = repository\n}\n\ntype templateWithProjectName struct {\n\ttemplateBase\n\tprojectName string\n}\n\nfunc (t *templateWithProjectName) InjectProjectName(projectName string) {\n\tt.projectName = projectName\n}\n\ntype templateWithMultiGroup struct {\n\ttemplateBase\n\tmultiGroup bool\n}\n\nfunc (t *templateWithMultiGroup) InjectMultiGroup(multiGroup bool) {\n\tt.multiGroup = multiGroup\n}\n\ntype templateWithBoilerplate struct {\n\ttemplateBase\n\tboilerplate string\n}\n\nfunc (t *templateWithBoilerplate) InjectBoilerplate(boilerplate string) {\n\tt.boilerplate = boilerplate\n}\n\ntype templateWithResource struct {\n\ttemplateBase\n\tresource *resource.Resource\n}\n\nfunc (t *templateWithResource) InjectResource(res *resource.Resource) {\n\tt.resource = res\n}\n\nvar _ = Describe(\"injector\", func() {\n\tvar tmp templateBase\n\n\tBeforeEach(func() {\n\t\ttmp = templateBase{\n\t\t\tpath:           \"my/path/to/file\",\n\t\t\tifExistsAction: Error,\n\t\t}\n\t})\n\n\tContext(\"injectInto\", func() {\n\t\tContext(\"Config\", func() {\n\t\t\tvar c config.Config\n\n\t\t\tBeforeEach(func() {\n\t\t\t\tc = cfgv3.New()\n\t\t\t})\n\n\t\t\tContext(\"Domain\", func() {\n\t\t\t\tvar template *templateWithDomain\n\n\t\t\t\tBeforeEach(func() {\n\t\t\t\t\ttemplate = &templateWithDomain{templateBase: tmp}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config is nil\", func() {\n\t\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\t\tExpect(template.domain).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config doesn't have a domain set\", func() {\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.domain).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should inject if the config has a domain set\", func() {\n\t\t\t\t\tconst domain = \"my.domain\"\n\t\t\t\t\tExpect(c.SetDomain(domain)).To(Succeed())\n\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.domain).To(Equal(domain))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"Repository\", func() {\n\t\t\t\tvar template *templateWithRepository\n\n\t\t\t\tBeforeEach(func() {\n\t\t\t\t\ttemplate = &templateWithRepository{templateBase: tmp}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config is nil\", func() {\n\t\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\t\tExpect(template.repository).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config doesn't have a repository set\", func() {\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.repository).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should inject if the config has a repository set\", func() {\n\t\t\t\t\tconst repo = \"test\"\n\t\t\t\t\tExpect(c.SetRepository(repo)).To(Succeed())\n\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.repository).To(Equal(repo))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"Project name\", func() {\n\t\t\t\tvar template *templateWithProjectName\n\n\t\t\t\tBeforeEach(func() {\n\t\t\t\t\ttemplate = &templateWithProjectName{templateBase: tmp}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config is nil\", func() {\n\t\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\t\tExpect(template.projectName).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config doesn't have a project name set\", func() {\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.projectName).To(Equal(\"\"))\n\t\t\t\t})\n\n\t\t\t\tIt(\"should inject if the config has a project name set\", func() {\n\t\t\t\t\tconst projectName = \"my project\"\n\t\t\t\t\tExpect(c.SetProjectName(projectName)).To(Succeed())\n\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.projectName).To(Equal(projectName))\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tContext(\"Multi-group\", func() {\n\t\t\t\tvar template *templateWithMultiGroup\n\n\t\t\t\tBeforeEach(func() {\n\t\t\t\t\ttemplate = &templateWithMultiGroup{templateBase: tmp}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not inject anything if the config is nil\", func() {\n\t\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\t\tExpect(template.multiGroup).To(BeFalse())\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not set the flag if the config doesn't have the multi-group flag set\", func() {\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.multiGroup).To(BeFalse())\n\t\t\t\t})\n\n\t\t\t\tIt(\"should set the flag if the config has the multi-group flag set\", func() {\n\t\t\t\t\tExpect(c.SetMultiGroup()).To(Succeed())\n\n\t\t\t\t\tinjector{config: c}.injectInto(template)\n\t\t\t\t\tExpect(template.multiGroup).To(BeTrue())\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Boilerplate\", func() {\n\t\t\tvar template *templateWithBoilerplate\n\n\t\t\tBeforeEach(func() {\n\t\t\t\ttemplate = &templateWithBoilerplate{templateBase: tmp}\n\t\t\t})\n\n\t\t\tIt(\"should not inject anything if no boilerplate was set\", func() {\n\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\tExpect(template.boilerplate).To(Equal(\"\"))\n\t\t\t})\n\n\t\t\tIt(\"should inject if the a boilerplate was set\", func() {\n\t\t\t\tconst boilerplate = `Copyright \"The Kubernetes Authors\"`\n\n\t\t\t\tinjector{boilerplate: boilerplate}.injectInto(template)\n\t\t\t\tExpect(template.boilerplate).To(Equal(boilerplate))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Resource\", func() {\n\t\t\tvar template *templateWithResource\n\n\t\t\tBeforeEach(func() {\n\t\t\t\ttemplate = &templateWithResource{templateBase: tmp}\n\t\t\t})\n\n\t\t\tIt(\"should not inject anything if the resource is nil\", func() {\n\t\t\t\tinjector{}.injectInto(template)\n\t\t\t\tExpect(template.resource).To(BeNil())\n\t\t\t})\n\n\t\t\tIt(\"should inject if the config has a domain set\", func() {\n\t\t\t\tres := &resource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   \"group\",\n\t\t\t\t\t\tDomain:  \"my.domain\",\n\t\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\t\tKind:    \"Kind\",\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tinjector{resource: res}.injectInto(template)\n\t\t\t\tExpect(template.resource).To(Equal(res))\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/interfaces.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"text/template\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// Builder defines the basic methods that any file builder must implement\ntype Builder interface {\n\t// GetPath returns the path to the file location\n\tGetPath() string\n\t// GetIfExistsAction returns the behavior when creating a file that already exists\n\tGetIfExistsAction() IfExistsAction\n}\n\n// RequiresValidation is a file builder that requires validation\ntype RequiresValidation interface {\n\tBuilder\n\t// Validate returns true if the template has valid values\n\tValidate() error\n}\n\n// Template is file builder based on a file template\ntype Template interface {\n\tBuilder\n\t// GetBody returns the template body\n\tGetBody() string\n\t// SetTemplateDefaults sets the default values for templates\n\tSetTemplateDefaults() error\n\t// SetDelim sets an action delimiters to replace default delimiters: {{ }}\n\tSetDelim(left, right string)\n\t// GetDelim returns the alternative delimiters\n\tGetDelim() (string, string)\n}\n\n// Inserter is a file builder that inserts code fragments in marked positions\ntype Inserter interface {\n\tBuilder\n\t// GetMarkers returns the different markers where code fragments will be inserted\n\tGetMarkers() []Marker\n\t// GetCodeFragments returns a map that binds markers to code fragments\n\tGetCodeFragments() CodeFragmentsMap\n}\n\n// HasIfNotExistsAction allows a template to define an action if the file is missing\ntype HasIfNotExistsAction interface {\n\tGetIfNotExistsAction() IfNotExistsAction\n}\n\n// HasDomain allows the domain to be used on a template\ntype HasDomain interface {\n\t// InjectDomain sets the template domain\n\tInjectDomain(string)\n}\n\n// HasRepository allows the repository to be used on a template\ntype HasRepository interface {\n\t// InjectRepository sets the template repository\n\tInjectRepository(string)\n}\n\n// HasProjectName allows a project name to be used on a template.\ntype HasProjectName interface {\n\t// InjectProjectName sets the template project name.\n\tInjectProjectName(string)\n}\n\n// HasMultiGroup allows the multi-group flag to be used on a template\ntype HasMultiGroup interface {\n\t// InjectMultiGroup sets the template multi-group flag\n\tInjectMultiGroup(bool)\n}\n\n// HasNamespaced allows the namespaced flag to be used on a template\ntype HasNamespaced interface {\n\t// InjectNamespaced sets the template namespaced flag\n\tInjectNamespaced(bool)\n}\n\n// HasBoilerplate allows a boilerplate to be used on a template\ntype HasBoilerplate interface {\n\t// InjectBoilerplate sets the template boilerplate\n\tInjectBoilerplate(string)\n}\n\n// HasResource allows a resource to be used on a template\ntype HasResource interface {\n\t// InjectResource sets the template resource\n\tInjectResource(*resource.Resource)\n}\n\n// UseCustomFuncMap allows a template to use a custom template.FuncMap instead of the default FuncMap.\ntype UseCustomFuncMap interface {\n\t// GetFuncMap returns a custom FuncMap.\n\tGetFuncMap() template.FuncMap\n}\n"
  },
  {
    "path": "pkg/machinery/machinery_suite_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestMachinery(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Machinery suite\")\n}\n"
  },
  {
    "path": "pkg/machinery/marker.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nconst kbPrefix = \"+kubebuilder:scaffold:\"\n\nvar commentsByExt = map[string]string{\n\t\".go\":   \"//\",\n\t\".yaml\": \"#\",\n\t\".yml\":  \"#\",\n\t// When adding additional file extensions, update also the NewMarkerFor documentation and error\n}\n\n// Marker represents a machine-readable comment that will be used for scaffolding purposes\ntype Marker struct {\n\tprefix  string\n\tcomment string\n\tvalue   string\n}\n\n// NewMarkerFor creates a new marker customized for the specific file. The created marker\n// is prefixed with `+kubebuilder:scaffold:` the default prefix for kubebuilder.\n// Supported file extensions: .go, .yaml, .yml.\nfunc NewMarkerFor(path string, value string) Marker {\n\treturn NewMarkerWithPrefixFor(kbPrefix, path, value)\n}\n\n// NewMarkerWithPrefixFor creates a new custom prefixed marker customized for the specific file\n// Supported file extensions: .go, .yaml, .yml\nfunc NewMarkerWithPrefixFor(prefix string, path string, value string) Marker {\n\text := filepath.Ext(path)\n\tif comment, found := commentsByExt[ext]; found {\n\t\treturn Marker{\n\t\t\tprefix:  markerPrefix(prefix),\n\t\t\tcomment: comment,\n\t\t\tvalue:   value,\n\t\t}\n\t}\n\n\textensions := make([]string, 0, len(commentsByExt))\n\tfor extension := range commentsByExt {\n\t\textensions = append(extensions, fmt.Sprintf(\"%q\", extension))\n\t}\n\tpanic(fmt.Errorf(\"unknown file extension: '%s', expected one of: %s\", ext, strings.Join(extensions, \", \")))\n}\n\n// String implements Stringer\nfunc (m Marker) String() string {\n\treturn m.comment + \" \" + m.prefix + m.value\n}\n\n// EqualsLine compares a marker with a string representation to check if they are the same marker\nfunc (m Marker) EqualsLine(line string) bool {\n\tline = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), m.comment))\n\treturn line == m.prefix+m.value\n}\n\n// CodeFragments represents a set of code fragments\n// A code fragment is a piece of code provided as a Go string, it may have multiple lines\ntype CodeFragments []string\n\n// CodeFragmentsMap binds Markers and CodeFragments together\ntype CodeFragmentsMap map[Marker]CodeFragments\n\nfunc markerPrefix(prefix string) string {\n\ttrimmed := strings.TrimSpace(prefix)\n\tvar builder strings.Builder\n\tif !strings.HasPrefix(trimmed, \"+\") {\n\t\tbuilder.WriteString(\"+\")\n\t}\n\tbuilder.WriteString(trimmed)\n\tif !strings.HasSuffix(trimmed, \":\") {\n\t\tbuilder.WriteString(\":\")\n\t}\n\n\treturn builder.String()\n}\n"
  },
  {
    "path": "pkg/machinery/marker_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"NewMarkerFor\", func() {\n\tDescribeTable(\"should create valid markers for known extensions\",\n\t\tfunc(path, comment string) { Expect(NewMarkerFor(path, \"\").comment).To(Equal(comment)) },\n\t\tEntry(\"for go files\", \"file.go\", \"//\"),\n\t\tEntry(\"for yaml files\", \"file.yaml\", \"#\"),\n\t\tEntry(\"for yaml files (short version)\", \"file.yml\", \"#\"),\n\t)\n\n\tIt(\"should panic for unknown extensions\", func() {\n\t\t// testing panics require to use a function with no arguments\n\t\tExpect(func() { NewMarkerFor(\"file.unkownext\", \"\") }).To(Panic())\n\t})\n})\n\nvar _ = Describe(\"Marker\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the right string representation\",\n\t\t\tfunc(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) },\n\t\t\tEntry(\"for go files\", Marker{prefix: kbPrefix, comment: \"//\", value: \"test\"},\n\t\t\t\t\"// +kubebuilder:scaffold:test\"),\n\t\t\tEntry(\"for yaml files\", Marker{prefix: kbPrefix, comment: \"#\", value: \"test\"},\n\t\t\t\t\"# +kubebuilder:scaffold:test\"),\n\t\t)\n\t})\n})\n\nvar _ = Describe(\"NewMarkerFor\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the right string representation\",\n\t\t\tfunc(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) },\n\t\t\tEntry(\"for yaml files\", NewMarkerFor(\"test.yaml\", \"test\"),\n\t\t\t\t\"# +kubebuilder:scaffold:test\"),\n\t\t)\n\t})\n})\n\nvar _ = Describe(\"NewMarkerForImports\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the correct string representation for import markers\",\n\t\t\tfunc(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) },\n\t\t\tEntry(\"for go import marker\", NewMarkerFor(\"test.go\", \"import \\\"my/package\\\"\"),\n\t\t\t\t\"// +kubebuilder:scaffold:import \\\"my/package\\\"\"),\n\t\t\tEntry(\"for go import marker with alias\", NewMarkerFor(\"test.go\",\n\t\t\t\t\"import alias \\\"my/package\\\"\"), \"// +kubebuilder:scaffold:import alias \\\"my/package\\\"\"),\n\t\t\tEntry(\"for multiline go import marker\", NewMarkerFor(\"test.go\",\n\t\t\t\t\"import (\\n\\\"my/package\\\"\\n)\"), \"// +kubebuilder:scaffold:import (\\n\\\"my/package\\\"\\n)\"),\n\t\t\tEntry(\"for multiline go import marker with alias\", NewMarkerFor(\"test.go\",\n\t\t\t\t\"import (\\nalias \\\"my/package\\\"\\n)\"), \"// +kubebuilder:scaffold:import (\\nalias \\\"my/package\\\"\\n)\"),\n\t\t)\n\t})\n\n\tIt(\"should detect import in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import \\\"my/package\\\"\"\n\t\tmarker := NewMarkerFor(\"test.go\", \"import \\\"my/package\\\"\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n\n\tIt(\"should detect import with alias in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import alias \\\"my/package\\\"\"\n\t\tmarker := NewMarkerFor(\"test.go\", \"import alias \\\"my/package\\\"\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n\n\tIt(\"should detect multiline import in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import (\\n\\\"my/package\\\"\\n)\"\n\t\tmarker := NewMarkerFor(\"test.go\", \"import (\\n\\\"my/package\\\"\\n)\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n\n\tIt(\"should detect multiline import with alias in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import (\\nalias \\\"my/package\\\"\\n)\"\n\t\tmarker := NewMarkerFor(\"test.go\",\n\t\t\t\"import (\\nalias \\\"my/package\\\"\\n)\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n})\n\nvar _ = Describe(\"NewMarkerForImports with different formatting\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should handle variations in spacing and formatting for import markers\",\n\t\t\tfunc(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) },\n\t\t\tEntry(\"go import marker with extra spaces\",\n\t\t\t\tNewMarkerFor(\"test.go\", \"import  \\\"my/package\\\"\"),\n\t\t\t\t\"// +kubebuilder:scaffold:import  \\\"my/package\\\"\"),\n\t\t\tEntry(\"go import marker with spaces around alias\",\n\t\t\t\tNewMarkerFor(\"test.go\", \"import  alias   \\\"my/package\\\"\"),\n\t\t\t\t\"// +kubebuilder:scaffold:import  alias   \\\"my/package\\\"\"),\n\t\t\tEntry(\"go import marker with newline\",\n\t\t\t\tNewMarkerFor(\"test.go\", \"import \\n\\\"my/package\\\"\"),\n\t\t\t\t\"// +kubebuilder:scaffold:import \\n\\\"my/package\\\"\"),\n\t\t)\n\t})\n\n\tIt(\"should detect import with spaces in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import  \\\"my/package\\\"\"\n\t\tmarker := NewMarkerFor(\"test.go\", \"import  \\\"my/package\\\"\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n\n\tIt(\"should detect import with alias and spaces in Go file\", func() {\n\t\tline := \"// +kubebuilder:scaffold:import  alias   \\\"my/package\\\"\"\n\t\tmarker := NewMarkerFor(\"test.go\",\n\t\t\t\"import  alias   \\\"my/package\\\"\")\n\t\tExpect(marker.EqualsLine(line)).To(BeTrue())\n\t})\n})\n\nvar _ = Describe(\"NewMarkerWithPrefixFor\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the right string representation\",\n\t\t\tfunc(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) },\n\n\t\t\tEntry(\"for yaml files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"custom:scaffold\",\n\t\t\t\t\t\"test.yaml\", \"test\"), \"# +custom:scaffold:test\"),\n\t\t\tEntry(\"for yaml files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"+custom:scaffold\",\n\t\t\t\t\t\"test.yaml\", \"test\"), \"# +custom:scaffold:test\"),\n\t\t\tEntry(\"for yaml files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"custom:scaffold:\",\n\t\t\t\t\t\"test.yaml\", \"test\"), \"# +custom:scaffold:test\"),\n\t\t\tEntry(\"for yaml files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"+custom:scaffold:\",\n\t\t\t\t\t\"test.yaml\", \"test\"), \"# +custom:scaffold:test\"),\n\t\t\tEntry(\"for yaml files\",\n\t\t\t\tNewMarkerWithPrefixFor(\" +custom:scaffold: \",\n\t\t\t\t\t\"test.yaml\", \"test\"), \"# +custom:scaffold:test\"),\n\n\t\t\tEntry(\"for go files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"custom:scaffold\",\n\t\t\t\t\t\"test.go\", \"test\"), \"// +custom:scaffold:test\"),\n\t\t\tEntry(\"for go files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"+custom:scaffold\",\n\t\t\t\t\t\"test.go\", \"test\"), \"// +custom:scaffold:test\"),\n\t\t\tEntry(\"for go files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"custom:scaffold:\",\n\t\t\t\t\t\"test.go\", \"test\"), \"// +custom:scaffold:test\"),\n\t\t\tEntry(\"for go files\",\n\t\t\t\tNewMarkerWithPrefixFor(\"+custom:scaffold:\",\n\t\t\t\t\t\"test.go\", \"test\"), \"// +custom:scaffold:test\"),\n\t\t\tEntry(\"for go files\",\n\t\t\t\tNewMarkerWithPrefixFor(\" +custom:scaffold: \",\n\t\t\t\t\t\"test.go\", \"test\"), \"// +custom:scaffold:test\"),\n\t\t)\n\t})\n})\n\nvar _ = Describe(\"NewMarkerFor with unsupported extensions\", func() {\n\tIt(\"should panic for unsupported extensions\", func() {\n\t\tExpect(func() { NewMarkerFor(\"file.txt\", \"test\") }).To(Panic())\n\t\tExpect(func() { NewMarkerFor(\"file.md\", \"test\") }).To(Panic())\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/mixins.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// PathMixin provides file builders with a path field\ntype PathMixin struct {\n\t// Path is the of the file\n\tPath string\n}\n\n// GetPath implements Builder\nfunc (t *PathMixin) GetPath() string {\n\treturn t.Path\n}\n\n// IfExistsActionMixin provides file builders with a if-exists-action field\ntype IfExistsActionMixin struct {\n\t// IfExistsAction determines what to do if the file exists\n\tIfExistsAction IfExistsAction\n}\n\n// GetIfExistsAction implements Builder\nfunc (t *IfExistsActionMixin) GetIfExistsAction() IfExistsAction {\n\treturn t.IfExistsAction\n}\n\n// TemplateMixin is the mixin that should be embedded in Template builders\ntype TemplateMixin struct {\n\tPathMixin\n\tIfExistsActionMixin\n\n\t// TemplateBody is the template body to execute\n\tTemplateBody    string\n\tparseDelimLeft  string\n\tparseDelimRight string\n}\n\n// GetBody implements Template\nfunc (t *TemplateMixin) GetBody() string {\n\treturn t.TemplateBody\n}\n\n// SetDelim implements Template\nfunc (t *TemplateMixin) SetDelim(left, right string) {\n\tt.parseDelimLeft = left\n\tt.parseDelimRight = right\n}\n\n// GetDelim implements Template\nfunc (t *TemplateMixin) GetDelim() (string, string) {\n\treturn t.parseDelimLeft, t.parseDelimRight\n}\n\n// InserterMixin is the mixin that should be embedded in Inserter builders\ntype InserterMixin struct {\n\tPathMixin\n}\n\n// GetIfExistsAction implements Builder\nfunc (t *InserterMixin) GetIfExistsAction() IfExistsAction {\n\t// Inserter builders always need to overwrite previous files\n\treturn OverwriteFile\n}\n\n// DomainMixin provides templates with a injectable domain field\ntype DomainMixin struct {\n\t// Domain is the domain for the APIs\n\tDomain string\n}\n\n// InjectDomain implements HasDomain\nfunc (m *DomainMixin) InjectDomain(domain string) {\n\tif m.Domain == \"\" {\n\t\tm.Domain = domain\n\t}\n}\n\n// RepositoryMixin provides templates with a injectable repository field\ntype RepositoryMixin struct {\n\t// Repo is the go project package path\n\tRepo string\n}\n\n// InjectRepository implements HasRepository\nfunc (m *RepositoryMixin) InjectRepository(repository string) {\n\tif m.Repo == \"\" {\n\t\tm.Repo = repository\n\t}\n}\n\n// ProjectNameMixin provides templates with an injectable project name field.\ntype ProjectNameMixin struct {\n\tProjectName string\n}\n\n// InjectProjectName implements HasProjectName.\nfunc (m *ProjectNameMixin) InjectProjectName(projectName string) {\n\tif m.ProjectName == \"\" {\n\t\tm.ProjectName = projectName\n\t}\n}\n\n// MultiGroupMixin provides templates with a injectable multi-group flag field\ntype MultiGroupMixin struct {\n\t// MultiGroup is the multi-group flag\n\tMultiGroup bool\n}\n\n// InjectMultiGroup implements HasMultiGroup\nfunc (m *MultiGroupMixin) InjectMultiGroup(flag bool) {\n\tm.MultiGroup = flag\n}\n\n// NamespacedMixin provides templates with a injectable namespaced flag field\ntype NamespacedMixin struct {\n\t// Namespaced is the namespaced flag\n\tNamespaced bool\n}\n\n// InjectNamespaced implements HasNamespaced\nfunc (m *NamespacedMixin) InjectNamespaced(flag bool) {\n\tm.Namespaced = flag\n}\n\n// BoilerplateMixin provides templates with a injectable boilerplate field\ntype BoilerplateMixin struct {\n\t// Boilerplate is the contents of a Boilerplate go header file\n\tBoilerplate string\n}\n\n// InjectBoilerplate implements HasBoilerplate\nfunc (m *BoilerplateMixin) InjectBoilerplate(boilerplate string) {\n\tif m.Boilerplate == \"\" {\n\t\tm.Boilerplate = boilerplate\n\t}\n}\n\n// ResourceMixin provides templates with a injectable resource field\ntype ResourceMixin struct {\n\tResource *resource.Resource\n}\n\n// InjectResource implements HasResource\nfunc (m *ResourceMixin) InjectResource(res *resource.Resource) {\n\tif m.Resource == nil {\n\t\tm.Resource = res\n\t}\n}\n\n// IfNotExistsActionMixin provides file builders with an if-not-exists-action field\ntype IfNotExistsActionMixin struct {\n\t// IfNotExistsAction determines what to do if the file does not exist\n\tIfNotExistsAction IfNotExistsAction\n}\n\n// GetIfNotExistsAction implements Inserter\nfunc (m *IfNotExistsActionMixin) GetIfNotExistsAction() IfNotExistsAction {\n\treturn m.IfNotExistsAction\n}\n"
  },
  {
    "path": "pkg/machinery/mixins_delim_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"TemplateMixin Delimiters\", func() {\n\tvar tmp TemplateMixin\n\n\tBeforeEach(func() {\n\t\ttmp = TemplateMixin{}\n\t})\n\n\tContext(\"SetDelim and GetDelim\", func() {\n\t\tIt(\"should set and get custom delimiters\", func() {\n\t\t\ttmp.SetDelim(\"[[\", \"]]\")\n\t\t\tleft, right := tmp.GetDelim()\n\t\t\tExpect(left).To(Equal(\"[[\"))\n\t\t\tExpect(right).To(Equal(\"]]\"))\n\t\t})\n\n\t\tIt(\"should return empty strings when delimiters are not set\", func() {\n\t\t\tleft, right := tmp.GetDelim()\n\t\t\tExpect(left).To(Equal(\"\"))\n\t\t\tExpect(right).To(Equal(\"\"))\n\t\t})\n\n\t\tIt(\"should allow setting delimiters multiple times\", func() {\n\t\t\ttmp.SetDelim(\"[[\", \"]]\")\n\t\t\tleft, right := tmp.GetDelim()\n\t\t\tExpect(left).To(Equal(\"[[\"))\n\t\t\tExpect(right).To(Equal(\"]]\"))\n\n\t\t\ttmp.SetDelim(\"<%\", \"%>\")\n\t\t\tleft, right = tmp.GetDelim()\n\t\t\tExpect(left).To(Equal(\"<%\"))\n\t\t\tExpect(right).To(Equal(\"%>\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"Mixins injection behaviors\", func() {\n\tContext(\"DomainMixin\", func() {\n\t\tIt(\"should not overwrite existing domain\", func() {\n\t\t\ttmp := DomainMixin{Domain: \"existing.domain\"}\n\t\t\ttmp.InjectDomain(\"new.domain\")\n\t\t\tExpect(tmp.Domain).To(Equal(\"existing.domain\"))\n\t\t})\n\n\t\tIt(\"should inject domain when empty\", func() {\n\t\t\ttmp := DomainMixin{}\n\t\t\ttmp.InjectDomain(\"new.domain\")\n\t\t\tExpect(tmp.Domain).To(Equal(\"new.domain\"))\n\t\t})\n\t})\n\n\tContext(\"RepositoryMixin\", func() {\n\t\tIt(\"should not overwrite existing repository\", func() {\n\t\t\ttmp := RepositoryMixin{Repo: \"existing.repo\"}\n\t\t\ttmp.InjectRepository(\"new.repo\")\n\t\t\tExpect(tmp.Repo).To(Equal(\"existing.repo\"))\n\t\t})\n\n\t\tIt(\"should inject repository when empty\", func() {\n\t\t\ttmp := RepositoryMixin{}\n\t\t\ttmp.InjectRepository(\"new.repo\")\n\t\t\tExpect(tmp.Repo).To(Equal(\"new.repo\"))\n\t\t})\n\t})\n\n\tContext(\"ProjectNameMixin\", func() {\n\t\tIt(\"should not overwrite existing project name\", func() {\n\t\t\ttmp := ProjectNameMixin{ProjectName: \"existing\"}\n\t\t\ttmp.InjectProjectName(\"new\")\n\t\t\tExpect(tmp.ProjectName).To(Equal(\"existing\"))\n\t\t})\n\n\t\tIt(\"should inject project name when empty\", func() {\n\t\t\ttmp := ProjectNameMixin{}\n\t\t\ttmp.InjectProjectName(\"new\")\n\t\t\tExpect(tmp.ProjectName).To(Equal(\"new\"))\n\t\t})\n\t})\n\n\tContext(\"BoilerplateMixin\", func() {\n\t\tIt(\"should not overwrite existing boilerplate\", func() {\n\t\t\ttmp := BoilerplateMixin{Boilerplate: \"existing\"}\n\t\t\ttmp.InjectBoilerplate(\"new\")\n\t\t\tExpect(tmp.Boilerplate).To(Equal(\"existing\"))\n\t\t})\n\n\t\tIt(\"should inject boilerplate when empty\", func() {\n\t\t\ttmp := BoilerplateMixin{}\n\t\t\ttmp.InjectBoilerplate(\"new\")\n\t\t\tExpect(tmp.Boilerplate).To(Equal(\"new\"))\n\t\t})\n\t})\n\n\tContext(\"ResourceMixin\", func() {\n\t\tIt(\"should not overwrite existing resource\", func() {\n\t\t\texisting := &resource.Resource{GVK: resource.GVK{Group: \"existing\"}}\n\t\t\ttmp := ResourceMixin{Resource: existing}\n\t\t\ttmp.InjectResource(&resource.Resource{GVK: resource.GVK{Group: \"new\"}})\n\t\t\tExpect(tmp.Resource.Group).To(Equal(\"existing\"))\n\t\t})\n\n\t\tIt(\"should inject resource when nil\", func() {\n\t\t\ttmp := ResourceMixin{}\n\t\t\tres := &resource.Resource{GVK: resource.GVK{Group: \"new\"}}\n\t\t\ttmp.InjectResource(res)\n\t\t\tExpect(tmp.Resource.Group).To(Equal(\"new\"))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"IfNotExistsActionMixin\", func() {\n\tContext(\"GetIfNotExistsAction\", func() {\n\t\tIt(\"should return the configured action\", func() {\n\t\t\ttmp := IfNotExistsActionMixin{IfNotExistsAction: IgnoreFile}\n\t\t\tExpect(tmp.GetIfNotExistsAction()).To(Equal(IgnoreFile))\n\t\t})\n\n\t\tIt(\"should return zero value when not set\", func() {\n\t\t\ttmp := IfNotExistsActionMixin{}\n\t\t\tExpect(tmp.GetIfNotExistsAction()).To(Equal(IfNotExistsAction(0)))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/mixins_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\ntype mockTemplate struct {\n\tTemplateMixin\n\tDomainMixin\n\tRepositoryMixin\n\tProjectNameMixin\n\tMultiGroupMixin\n\tBoilerplateMixin\n\tResourceMixin\n}\n\ntype mockInserter struct {\n\t// InserterMixin requires a different type because it collides with TemplateMixin\n\tInserterMixin\n}\n\nvar _ = Describe(\"TemplateMixin\", func() {\n\tconst (\n\t\tpath           = \"path/to/file.go\"\n\t\tifExistsAction = SkipFile\n\t\tbody           = \"content\"\n\t)\n\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{\n\t\t\tTemplateMixin: TemplateMixin{\n\t\t\t\tPathMixin:           PathMixin{path},\n\t\t\t\tIfExistsActionMixin: IfExistsActionMixin{ifExistsAction},\n\t\t\t\tTemplateBody:        body,\n\t\t\t},\n\t\t}\n\t})\n\n\tContext(\"GetPath\", func() {\n\t\tIt(\"should return the path\", func() {\n\t\t\tExpect(tmp.GetPath()).To(Equal(path))\n\t\t})\n\t})\n\n\tContext(\"GetIfExistsAction\", func() {\n\t\tIt(\"should return the if-exists action\", func() {\n\t\t\tExpect(tmp.GetIfExistsAction()).To(Equal(ifExistsAction))\n\t\t})\n\t})\n\n\tContext(\"GetBody\", func() {\n\t\tIt(\"should return the body\", func() {\n\t\t\tExpect(tmp.GetBody()).To(Equal(body))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"InserterMixin\", func() {\n\tconst path = \"path/to/file.go\"\n\n\tvar tmp mockInserter\n\n\tBeforeEach(func() {\n\t\ttmp = mockInserter{\n\t\t\tInserterMixin: InserterMixin{\n\t\t\t\tPathMixin: PathMixin{path},\n\t\t\t},\n\t\t}\n\t})\n\n\tContext(\"GetPath\", func() {\n\t\tIt(\"should return the path\", func() {\n\t\t\tExpect(tmp.GetPath()).To(Equal(path))\n\t\t})\n\t})\n\n\tContext(\"GetIfExistsAction\", func() {\n\t\tIt(\"should return overwrite file always\", func() {\n\t\t\tExpect(tmp.GetIfExistsAction()).To(Equal(OverwriteFile))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"DomainMixin\", func() {\n\tconst domain = \"my.domain\"\n\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectDomain\", func() {\n\t\tIt(\"should inject the provided domain\", func() {\n\t\t\ttmp.InjectDomain(domain)\n\t\t\tExpect(tmp.Domain).To(Equal(domain))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"RepositoryMixin\", func() {\n\tconst repo = \"test\"\n\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectRepository\", func() {\n\t\tIt(\"should inject the provided repository\", func() {\n\t\t\ttmp.InjectRepository(repo)\n\t\t\tExpect(tmp.Repo).To(Equal(repo))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"ProjectNameMixin\", func() {\n\tconst name = \"my project\"\n\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectProjectName\", func() {\n\t\tIt(\"should inject the provided project name\", func() {\n\t\t\ttmp.InjectProjectName(name)\n\t\t\tExpect(tmp.ProjectName).To(Equal(name))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"MultiGroupMixin\", func() {\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectMultiGroup\", func() {\n\t\tIt(\"should inject the provided multi group flag\", func() {\n\t\t\ttmp.InjectMultiGroup(true)\n\t\t\tExpect(tmp.MultiGroup).To(BeTrue())\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"BoilerplateMixin\", func() {\n\tconst boilerplate = \"Copyright\"\n\n\tvar tmp mockTemplate\n\n\tBeforeEach(func() {\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectBoilerplate\", func() {\n\t\tIt(\"should inject the provided boilerplate\", func() {\n\t\t\ttmp.InjectBoilerplate(boilerplate)\n\t\t\tExpect(tmp.Boilerplate).To(Equal(boilerplate))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"ResourceMixin\", func() {\n\tvar (\n\t\tres *resource.Resource\n\t\ttmp mockTemplate\n\t)\n\n\tBeforeEach(func() {\n\t\tres = &resource.Resource{GVK: resource.GVK{\n\t\t\tGroup:   \"group\",\n\t\t\tDomain:  \"my.domain\",\n\t\t\tVersion: \"v1\",\n\t\t\tKind:    \"Kind\",\n\t\t}}\n\n\t\ttmp = mockTemplate{}\n\t})\n\n\tContext(\"InjectResource\", func() {\n\t\tIt(\"should inject the provided resource\", func() {\n\t\t\ttmp.InjectResource(res)\n\t\t\tExpect(tmp.Resource.GVK.IsEqualTo(res.GVK)).To(BeTrue())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/machinery/scaffold.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/spf13/afero\"\n\t\"golang.org/x/tools/imports\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nconst (\n\tcreateOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC\n\n\t// DefaultDirectoryPermission and DefaultFilePermission are used so generated\n\t// files work in shared and container workflows. Use them when writing scaffolded\n\t// or config files for consistency.\n\tDefaultDirectoryPermission os.FileMode = 0o755\n\tDefaultFilePermission      os.FileMode = 0o644\n)\n\nvar options = imports.Options{\n\tComments:   true,\n\tTabIndent:  true,\n\tTabWidth:   8,\n\tFormatOnly: true,\n}\n\n// Scaffold uses templates to scaffold new files\ntype Scaffold struct {\n\t// fs allows to mock the file system for tests\n\tfs afero.Fs\n\n\t// permissions for new directories and files\n\tdirPerm  os.FileMode\n\tfilePerm os.FileMode\n\n\t// injector is used to provide several fields to the templates\n\tinjector injector\n}\n\n// ScaffoldOption allows to provide optional arguments to the Scaffold\ntype ScaffoldOption func(*Scaffold)\n\n// NewScaffold returns a new Scaffold with the provided plugins\nfunc NewScaffold(fs Filesystem, options ...ScaffoldOption) *Scaffold {\n\ts := &Scaffold{\n\t\tfs:       fs.FS,\n\t\tdirPerm:  DefaultDirectoryPermission,\n\t\tfilePerm: DefaultFilePermission,\n\t}\n\n\tfor _, option := range options {\n\t\toption(s)\n\t}\n\n\treturn s\n}\n\n// WithDirectoryPermissions sets the permissions for new directories\nfunc WithDirectoryPermissions(dirPerm os.FileMode) ScaffoldOption {\n\treturn func(s *Scaffold) {\n\t\ts.dirPerm = dirPerm\n\t}\n}\n\n// WithFilePermissions sets the permissions for new files\nfunc WithFilePermissions(filePerm os.FileMode) ScaffoldOption {\n\treturn func(s *Scaffold) {\n\t\ts.filePerm = filePerm\n\t}\n}\n\n// WithConfig provides the project configuration to the Scaffold\nfunc WithConfig(cfg config.Config) ScaffoldOption {\n\treturn func(s *Scaffold) {\n\t\ts.injector.config = cfg\n\n\t\tif cfg != nil && cfg.GetRepository() != \"\" {\n\t\t\timports.LocalPrefix = cfg.GetRepository()\n\t\t}\n\t}\n}\n\n// WithBoilerplate provides the boilerplate to the Scaffold\nfunc WithBoilerplate(boilerplate string) ScaffoldOption {\n\treturn func(s *Scaffold) {\n\t\ts.injector.boilerplate = boilerplate\n\t}\n}\n\n// WithResource provides the resource to the Scaffold\nfunc WithResource(res *resource.Resource) ScaffoldOption {\n\treturn func(s *Scaffold) {\n\t\ts.injector.resource = res\n\t}\n}\n\n// Execute writes to disk the provided files\nfunc (s *Scaffold) Execute(builders ...Builder) error {\n\t// Initialize the files\n\tfiles := make(map[string]*File, len(builders))\n\n\tfor _, builder := range builders {\n\t\t// Inject common fields\n\t\ts.injector.injectInto(builder)\n\n\t\t// Validate file builders\n\t\tif reqValBuilder, requiresValidation := builder.(RequiresValidation); requiresValidation {\n\t\t\tif err := reqValBuilder.Validate(); err != nil {\n\t\t\t\treturn ValidateError{err}\n\t\t\t}\n\t\t}\n\n\t\t// Build models for Template builders\n\t\tif t, isTemplate := builder.(Template); isTemplate {\n\t\t\tif err := s.buildFileModel(t, files); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// Build models for Inserter builders\n\t\tif i, isInserter := builder.(Inserter); isInserter {\n\t\t\tif err := s.updateFileModel(i, files); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Persist the files to disk\n\tfor _, f := range files {\n\t\tif err := s.writeFile(f); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// buildFileModel scaffolds a single file\nfunc (Scaffold) buildFileModel(t Template, models map[string]*File) error {\n\t// Set the template default values\n\tif err := t.SetTemplateDefaults(); err != nil {\n\t\treturn SetTemplateDefaultsError{err}\n\t}\n\n\tpath := t.GetPath()\n\n\t// Handle already existing models\n\tif _, found := models[path]; found {\n\t\tswitch t.GetIfExistsAction() {\n\t\tcase SkipFile:\n\t\t\treturn nil\n\t\tcase Error:\n\t\t\treturn ModelAlreadyExistsError{path}\n\t\tcase OverwriteFile:\n\t\tdefault:\n\t\t\treturn UnknownIfExistsActionError{path, t.GetIfExistsAction()}\n\t\t}\n\t}\n\n\tb, err := doTemplate(t)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmodels[path] = &File{\n\t\tPath:           path,\n\t\tContents:       string(b),\n\t\tIfExistsAction: t.GetIfExistsAction(),\n\t}\n\treturn nil\n}\n\n// doTemplate executes the template for a file using the input\nfunc doTemplate(t Template) ([]byte, error) {\n\t// Create a new template.Template using the type of the Template as the name\n\ttemp := template.New(fmt.Sprintf(\"%T\", t))\n\tleftDelim, rightDelim := t.GetDelim()\n\tif leftDelim != \"\" && rightDelim != \"\" {\n\t\ttemp.Delims(leftDelim, rightDelim)\n\t}\n\n\t// Set the function map to be used\n\tfm := DefaultFuncMap()\n\tif templateWithFuncMap, hasCustomFuncMap := t.(UseCustomFuncMap); hasCustomFuncMap {\n\t\tfm = templateWithFuncMap.GetFuncMap()\n\t}\n\ttemp.Funcs(fm)\n\n\t// Set the template body\n\tif _, err := temp.Parse(t.GetBody()); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse template: %w\", err)\n\t}\n\n\t// Execute the template\n\tout := &bytes.Buffer{}\n\tif err := temp.Execute(out, t); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute template: %w\", err)\n\t}\n\tb := out.Bytes()\n\n\t// TODO(adirio): move go-formatting to write step\n\t// gofmt the imports\n\tif filepath.Ext(t.GetPath()) == \".go\" {\n\t\tvar err error\n\t\tif b, err = imports.Process(t.GetPath(), b, &options); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to process template: %w\", err)\n\t\t}\n\t}\n\n\treturn b, nil\n}\n\n// updateFileModel updates a single file\nfunc (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error {\n\tm, err := s.loadPreviousModel(i, models)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\tif withOptionalBehavior, ok := i.(HasIfNotExistsAction); ok {\n\t\t\t\tswitch withOptionalBehavior.GetIfNotExistsAction() {\n\t\t\t\tcase IgnoreFile:\n\t\t\t\t\tlog.Warn(\"skipping missing file\", \"file\", i.GetPath())\n\t\t\t\t\tlog.Warn(\"the code fragments will not be inserted\")\n\t\t\t\t\treturn nil\n\t\t\t\tcase ErrorIfNotExist:\n\t\t\t\t\treturn err\n\t\t\t\tdefault:\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If inserter doesn't implement HasIfNotExistsAction, return the original error\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"failed to load previous model for %s: %w\", i.GetPath(), err)\n\t}\n\n\t// Get valid code fragments\n\tcodeFragments := getValidCodeFragments(i)\n\n\t// Remove code fragments that already were applied\n\terr = filterExistingValues(m.Contents, codeFragments)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to filter existing values: %w\", err)\n\t}\n\n\t// If no code fragment to insert, we are done\n\tif len(codeFragments) == 0 {\n\t\treturn nil\n\t}\n\n\tcontent, err := insertStrings(m.Contents, codeFragments)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to insert values: %w\", err)\n\t}\n\n\t// TODO(adirio): move go-formatting to write step\n\tformattedContent := content\n\tif ext := filepath.Ext(i.GetPath()); ext == \".go\" {\n\t\tformattedContent, err = imports.Process(i.GetPath(), content, nil)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to process formatted content: %w\", err)\n\t\t}\n\t}\n\n\tm.Contents = string(formattedContent)\n\tm.IfExistsAction = OverwriteFile\n\tmodels[m.Path] = m\n\treturn nil\n}\n\n// loadPreviousModel gets the previous model from the models map or the actual file\nfunc (s Scaffold) loadPreviousModel(i Inserter, models map[string]*File) (*File, error) {\n\tpath := i.GetPath()\n\n\t// Let's see if we already have a model for this file\n\tif m, found := models[path]; found {\n\t\t// Check if there is already a scaffolded file\n\t\texists, err := afero.Exists(s.fs, path)\n\t\tif err != nil {\n\t\t\treturn nil, ExistsFileError{err}\n\t\t}\n\n\t\t// If there is a model but no scaffolded file we return the model\n\t\tif !exists {\n\t\t\treturn m, nil\n\t\t}\n\n\t\t// If both a model and a file are found, check which has preference\n\t\tswitch m.IfExistsAction {\n\t\tcase SkipFile:\n\t\t\t// File has preference\n\t\t\tfromFile, err := s.loadModelFromFile(path)\n\t\t\tif err != nil {\n\t\t\t\treturn m, nil\n\t\t\t}\n\t\t\treturn fromFile, nil\n\t\tcase Error:\n\t\t\t// Writing will result in an error, so we can return error now\n\t\t\treturn nil, FileAlreadyExistsError{path}\n\t\tcase OverwriteFile:\n\t\t\t// Model has preference\n\t\t\treturn m, nil\n\t\tdefault:\n\t\t\treturn nil, UnknownIfExistsActionError{path, m.IfExistsAction}\n\t\t}\n\t}\n\n\t// There was no model\n\treturn s.loadModelFromFile(path)\n}\n\n// loadModelFromFile gets the previous model from the actual file\nfunc (s Scaffold) loadModelFromFile(path string) (f *File, err error) {\n\treader, err := s.fs.Open(path)\n\tif err != nil {\n\t\treturn nil, OpenFileError{err}\n\t}\n\tdefer func() {\n\t\tif closeErr := reader.Close(); err == nil && closeErr != nil {\n\t\t\terr = CloseFileError{closeErr}\n\t\t}\n\t}()\n\n\tcontent, err := afero.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, ReadFileError{err}\n\t}\n\n\treturn &File{Path: path, Contents: string(content)}, nil\n}\n\n// getValidCodeFragments obtains the code fragments from a file.Inserter\nfunc getValidCodeFragments(i Inserter) CodeFragmentsMap {\n\t// Get the code fragments\n\tcodeFragments := i.GetCodeFragments()\n\n\t// Validate the code fragments\n\tvalidMarkers := i.GetMarkers()\n\tfor marker := range codeFragments {\n\t\tvalid := slices.Contains(validMarkers, marker)\n\t\tif !valid {\n\t\t\tdelete(codeFragments, marker)\n\t\t}\n\t}\n\n\treturn codeFragments\n}\n\n// filterExistingValues removes code fragments that already exist in the content.\nfunc filterExistingValues(content string, codeFragmentsMap CodeFragmentsMap) error {\n\tfor marker, codeFragments := range codeFragmentsMap {\n\t\tcodeFragmentsOut := codeFragments[:0]\n\n\t\tfor _, codeFragment := range codeFragments {\n\t\t\texists, err := codeFragmentExists(content, codeFragment)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to check if code fragment exists: %w\", err)\n\t\t\t}\n\t\t\tif !exists {\n\t\t\t\tcodeFragmentsOut = append(codeFragmentsOut, codeFragment)\n\t\t\t}\n\t\t}\n\n\t\tif len(codeFragmentsOut) == 0 {\n\t\t\tdelete(codeFragmentsMap, marker)\n\t\t} else {\n\t\t\tcodeFragmentsMap[marker] = codeFragmentsOut\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// codeFragmentExists checks if the codeFragment exists in the content.\nfunc codeFragmentExists(content, codeFragment string) (exists bool, err error) {\n\t// Trim space on each line in order to match different levels of indentation.\n\tvar sb strings.Builder\n\tfor line := range strings.SplitSeq(codeFragment, \"\\n\") {\n\t\t_, _ = sb.WriteString(strings.TrimSpace(line))\n\t\t_ = sb.WriteByte('\\n')\n\t}\n\n\tcodeFragmentTrimmed := strings.TrimSpace(sb.String())\n\tscanLines := 1 + strings.Count(codeFragmentTrimmed, \"\\n\")\n\tscanFunc := func(contentGroup string) bool {\n\t\tif contentGroup == codeFragmentTrimmed {\n\t\t\texists = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tif scanMultilineErr := scanMultiline(content, scanLines, scanFunc); scanMultilineErr != nil {\n\t\treturn false, scanMultilineErr\n\t}\n\n\treturn exists, nil\n}\n\n// scanMultiline scans a string while buffering the specified number of scanLines. It calls scanFunc\n// for every group of lines. The content passed to scanFunc will have trimmed whitespace. It\n// continues scanning the content as long as scanFunc returns true.\nfunc scanMultiline(content string, scanLines int, scanFunc func(contentGroup string) bool) error {\n\tscanner := bufio.NewScanner(strings.NewReader(content))\n\n\t// Optimized simple case.\n\tif scanLines == 1 {\n\t\tfor scanner.Scan() {\n\t\t\tif !scanFunc(strings.TrimSpace(scanner.Text())) {\n\t\t\t\tif err := scanner.Err(); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to scan content: %w\", err)\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\tif err := scanner.Err(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to scan content: %w\", err)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\t// Complex case.\n\tbufferedLines := make([]string, scanLines)\n\tbufferedLinesIndex := 0\n\tvar sb strings.Builder\n\n\tfor scanner.Scan() {\n\t\t// Trim space on each line in order to match different levels of indentation.\n\t\tbufferedLines[bufferedLinesIndex] = strings.TrimSpace(scanner.Text())\n\t\tbufferedLinesIndex = (bufferedLinesIndex + 1) % scanLines\n\n\t\tsb.Reset()\n\t\tfor i := range scanLines {\n\t\t\t_, _ = sb.WriteString(bufferedLines[(bufferedLinesIndex+i)%scanLines])\n\t\t\t_ = sb.WriteByte('\\n')\n\t\t}\n\n\t\tif !scanFunc(strings.TrimSpace(sb.String())) {\n\t\t\tif err := scanner.Err(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to scan content: %w\", err)\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif err := scanner.Err(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scan content: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, error) {\n\tout := new(bytes.Buffer)\n\n\tscanner := bufio.NewScanner(strings.NewReader(content))\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\n\t\tfor marker, codeFragments := range codeFragmentsMap {\n\t\t\tif marker.EqualsLine(line) {\n\t\t\t\tfor _, codeFragment := range codeFragments {\n\t\t\t\t\t_, _ = out.WriteString(codeFragment) // bytes.Buffer.WriteString always returns nil errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_, _ = out.WriteString(line + \"\\n\") // bytes.Buffer.WriteString always returns nil errors\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to scan content: %w\", err)\n\t}\n\n\treturn out.Bytes(), nil\n}\n\nfunc (s Scaffold) writeFile(f *File) error {\n\t// Check if the file to write already exists\n\texists, err := afero.Exists(s.fs, f.Path)\n\tif err != nil {\n\t\treturn ExistsFileError{err}\n\t}\n\tif exists {\n\t\tswitch f.IfExistsAction {\n\t\tcase OverwriteFile:\n\t\t\t// By not returning, the file is written as if it didn't exist\n\t\tcase SkipFile:\n\t\t\t// By returning nil, the file is not written but the process will carry on\n\t\t\treturn nil\n\t\tcase Error:\n\t\t\t// By returning an error, the file is not written and the process will fail\n\t\t\treturn FileAlreadyExistsError{f.Path}\n\t\t}\n\t}\n\n\t// Create the directory if needed\n\tif err = s.fs.MkdirAll(filepath.Dir(f.Path), s.dirPerm); err != nil {\n\t\treturn CreateDirectoryError{err}\n\t}\n\n\t// Create or truncate the file\n\twriter, err := s.fs.OpenFile(f.Path, createOrUpdate, s.filePerm)\n\tif err != nil {\n\t\treturn CreateFileError{err}\n\t}\n\tdefer func() {\n\t\tif closeErr := writer.Close(); err == nil && closeErr != nil {\n\t\t\terr = CloseFileError{err}\n\t\t}\n\t}()\n\n\tif _, writeErr := writer.Write([]byte(f.Contents)); writeErr != nil {\n\t\treturn WriteFileError{writeErr}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/machinery/scaffold_test.go",
    "content": "/*\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage machinery\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"Scaffold\", func() {\n\tDescribe(\"NewScaffold\", func() {\n\t\tIt(\"should succeed for no option\", func() {\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()})\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(DefaultDirectoryPermission))\n\t\t\tExpect(s.filePerm).To(Equal(DefaultFilePermission))\n\t\t\tExpect(s.injector.config).To(BeNil())\n\t\t\tExpect(s.injector.boilerplate).To(Equal(\"\"))\n\t\t\tExpect(s.injector.resource).To(BeNil())\n\t\t})\n\n\t\tIt(\"should succeed with directory permissions option\", func() {\n\t\t\tconst dirPermissions os.FileMode = 0o755\n\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithDirectoryPermissions(dirPermissions))\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(dirPermissions))\n\t\t\tExpect(s.filePerm).To(Equal(DefaultFilePermission))\n\t\t\tExpect(s.injector.config).To(BeNil())\n\t\t\tExpect(s.injector.boilerplate).To(Equal(\"\"))\n\t\t\tExpect(s.injector.resource).To(BeNil())\n\t\t})\n\n\t\tIt(\"should succeed with file permissions option\", func() {\n\t\t\tconst filePermissions os.FileMode = 0o755\n\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithFilePermissions(filePermissions))\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(DefaultDirectoryPermission))\n\t\t\tExpect(s.filePerm).To(Equal(filePermissions))\n\t\t\tExpect(s.injector.config).To(BeNil())\n\t\t\tExpect(s.injector.boilerplate).To(Equal(\"\"))\n\t\t\tExpect(s.injector.resource).To(BeNil())\n\t\t})\n\n\t\tIt(\"should succeed with config option\", func() {\n\t\t\tcfg := cfgv3.New()\n\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithConfig(cfg))\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(DefaultDirectoryPermission))\n\t\t\tExpect(s.filePerm).To(Equal(DefaultFilePermission))\n\t\t\tExpect(s.injector.config).NotTo(BeNil())\n\t\t\tExpect(s.injector.config.GetVersion().Compare(cfgv3.Version)).To(Equal(0))\n\t\t\tExpect(s.injector.boilerplate).To(Equal(\"\"))\n\t\t\tExpect(s.injector.resource).To(BeNil())\n\t\t})\n\n\t\tIt(\"should succeed with boilerplate option\", func() {\n\t\t\tconst boilerplate = \"Copyright\"\n\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithBoilerplate(boilerplate))\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(DefaultDirectoryPermission))\n\t\t\tExpect(s.filePerm).To(Equal(DefaultFilePermission))\n\t\t\tExpect(s.injector.config).To(BeNil())\n\t\t\tExpect(s.injector.boilerplate).To(Equal(boilerplate))\n\t\t\tExpect(s.injector.resource).To(BeNil())\n\t\t})\n\n\t\tIt(\"should succeed with resource option\", func() {\n\t\t\tres := &resource.Resource{GVK: resource.GVK{\n\t\t\t\tGroup:   \"group\",\n\t\t\t\tDomain:  \"my.domain\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Kind\",\n\t\t\t}}\n\n\t\t\ts := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithResource(res))\n\t\t\tExpect(s.fs).NotTo(BeNil())\n\t\t\tExpect(s.dirPerm).To(Equal(DefaultDirectoryPermission))\n\t\t\tExpect(s.filePerm).To(Equal(DefaultFilePermission))\n\t\t\tExpect(s.injector.config).To(BeNil())\n\t\t\tExpect(s.injector.boilerplate).To(Equal(\"\"))\n\t\t\tExpect(s.injector.resource).NotTo(BeNil())\n\t\t\tExpect(s.injector.resource.GVK.IsEqualTo(res.GVK)).To(BeTrue())\n\t\t})\n\t})\n\n\tDescribe(\"Scaffold.Execute\", func() {\n\t\tconst (\n\t\t\tpath     = \"filename\"\n\t\t\tpathGo   = path + \".go\"\n\t\t\tpathYaml = path + \".yaml\"\n\t\t\tcontent  = \"Hello world!\"\n\t\t)\n\n\t\tvar (\n\t\t\ttestErr error\n\t\t\ts       *Scaffold\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\ttestErr = errors.New(\"error text\")\n\t\t\ts = &Scaffold{fs: afero.NewMemMapFs()}\n\t\t})\n\n\t\tDescribeTable(\"successes\",\n\t\t\tfunc(path, expected string, files ...Builder) {\n\t\t\t\tExpect(s.Execute(files...)).To(Succeed())\n\n\t\t\t\tb, err := afero.ReadFile(s.fs, path)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(expected))\n\t\t\t},\n\t\t\tEntry(\"should write the file\",\n\t\t\t\tpath, content,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content},\n\t\t\t),\n\t\t\tEntry(\"should skip optional models if already have one\",\n\t\t\t\tpath, content,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content},\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}},\n\t\t\t),\n\t\t\tEntry(\"should overwrite required models if already have one\",\n\t\t\t\tpath, content,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}},\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile}, body: content},\n\t\t\t),\n\t\t\tEntry(\"should format a go file\",\n\t\t\t\tpathGo, \"package file\\n\",\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: \"package    file\"},\n\t\t\t),\n\n\t\t\tEntry(\"should render actions correctly\",\n\t\t\t\tpath, \"package testValue\",\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path, TestField: \"testValue\"}, body: \"package {{.TestField}}\"},\n\t\t\t),\n\n\t\t\tEntry(\"should render actions with alternative delimiters correctly\",\n\t\t\t\tpath, \"package testValue\",\n\t\t\t\t&fakeTemplate{\n\t\t\t\t\tfakeBuilder:     fakeBuilder{path: path, TestField: \"testValue\"},\n\t\t\t\t\tbody:            \"package [[.TestField]]\",\n\t\t\t\t\tparseDelimLeft:  \"[[\",\n\t\t\t\t\tparseDelimRight: \"]]\",\n\t\t\t\t},\n\t\t\t),\n\t\t)\n\n\t\tDescribeTable(\"file builders related errors\",\n\t\t\tfunc(setup func() ([]Builder, error)) {\n\t\t\t\tfiles, errType := setup()\n\n\t\t\t\terr := s.Execute(files...)\n\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err).To(MatchError(errType))\n\t\t\t},\n\t\t\tEntry(\"should fail if unable to validate a file builder\", func() ([]Builder, error) {\n\t\t\t\treturn []Builder{\n\t\t\t\t\tfakeRequiresValidation{validateErr: testErr},\n\t\t\t\t}, ValidateError{testErr}\n\t\t\t}),\n\n\t\t\tEntry(\"should fail if unable to set default values for a template\", func() ([]Builder, error) {\n\t\t\t\treturn []Builder{\n\t\t\t\t\t&fakeTemplate{err: testErr},\n\t\t\t\t}, SetTemplateDefaultsError{testErr}\n\t\t\t}),\n\n\t\t\tEntry(\"should fail if an unexpected previous model is found\", func() ([]Builder, error) {\n\t\t\t\treturn []Builder{\n\t\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}},\n\t\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}},\n\t\t\t\t}, ModelAlreadyExistsError{path: path}\n\t\t\t}),\n\t\t\tEntry(\"should fail if behavior if-exists-action is not defined\", func() ([]Builder, error) {\n\t\t\t\treturn []Builder{\n\t\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path}},\n\t\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}},\n\t\t\t\t}, UnknownIfExistsActionError{path: path, ifExistsAction: -1}\n\t\t\t}),\n\t\t)\n\n\t\t// Following errors are unwrapped, so we need to check for substrings\n\t\tDescribeTable(\"template related errors\",\n\t\t\tfunc(errMsg string, files ...Builder) {\n\t\t\t\terr := s.Execute(files...)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(errMsg))\n\t\t\t},\n\t\t\tEntry(\"should fail if a template is broken\",\n\t\t\t\t\"template: \",\n\t\t\t\t&fakeTemplate{body: \"{{ .Field }\"},\n\t\t\t),\n\t\t\tEntry(\"should fail if a template params aren't provided\",\n\t\t\t\t\"template: \",\n\t\t\t\t&fakeTemplate{body: \"{{ .Field }}\"},\n\t\t\t),\n\t\t\tEntry(\"should fail if unable to format a go file\",\n\t\t\t\t\"expected 'package', found \",\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: content},\n\t\t\t),\n\t\t)\n\n\t\tDescribeTable(\"insert strings\",\n\t\t\tfunc(path, input, expected string, files ...Builder) {\n\t\t\t\tExpect(afero.WriteFile(s.fs, path, []byte(input), 0o666)).To(Succeed())\n\n\t\t\t\tExpect(s.Execute(files...)).To(Succeed())\n\n\t\t\t\tb, err := afero.ReadFile(s.fs, path)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(expected))\n\t\t\t},\n\t\t\tEntry(\"should insert lines for go files\",\n\t\t\t\tpathGo,\n\t\t\t\t`package test\n\n// +kubebuilder:scaffold:-\n`,\n\t\t\t\t`package test\n\nvar a int\nvar b int\n\n// +kubebuilder:scaffold:-\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathGo},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathGo, \"-\"): {\"var a int\\n\", \"var b int\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should insert lines for yaml files\",\n\t\t\t\tpathYaml,\n\t\t\t\t`\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should use models if there is no file\",\n\t\t\t\tpathYaml,\n\t\t\t\t\"\",\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: `\n# +kubebuilder:scaffold:-\n`},\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should use required models over files\",\n\t\t\t\tpathYaml,\n\t\t\t\tcontent,\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: `\n# +kubebuilder:scaffold:-\n`},\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should use files over optional models\",\n\t\t\t\tpathYaml,\n\t\t\t\t`\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml}, body: content},\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should filter invalid markers\",\n\t\t\t\tpathYaml,\n\t\t\t\t`\n# +kubebuilder:scaffold:-\n# +kubebuilder:scaffold:*\n`,\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n# +kubebuilder:scaffold:*\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tmarkers:     []Marker{NewMarkerFor(pathYaml, \"-\")},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"*\"): {\"3\\n\", \"4\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should filter already existing one-line code fragments\",\n\t\t\t\tpathYaml,\n\t\t\t\t`\n1\n# +kubebuilder:scaffold:-\n3\n4\n# +kubebuilder:scaffold:*\n`,\n\t\t\t\t`\n1\n2\n# +kubebuilder:scaffold:-\n3\n4\n# +kubebuilder:scaffold:*\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {\"1\\n\", \"2\\n\"},\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"*\"): {\"3\\n\", \"4\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should filter already existing multi-line indented code fragments\",\n\t\t\t\tpathGo,\n\t\t\t\t`package test\n\nfunc init() {\n\tif err := something(); err != nil {\n\t\treturn err\n\t}\n\t\n\t// +kubebuilder:scaffold:-\n}\n`,\n\t\t\t\t`package test\n\nfunc init() {\n\tif err := something(); err != nil {\n\t\treturn err\n\t}\n\t\n\t// +kubebuilder:scaffold:-\n}\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathGo},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathGo, \"-\"): {\"if err := something(); err != nil {\\n\\treturn err\\n}\\n\\n\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t\tEntry(\"should not insert anything if no code fragment\",\n\t\t\t\tpathYaml,\n\t\t\t\t`\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\t`\n# +kubebuilder:scaffold:-\n`,\n\t\t\t\tfakeInserter{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: pathYaml},\n\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\tNewMarkerFor(pathYaml, \"-\"): {},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t)\n\n\t\tDescribeTable(\"insert strings related errors\",\n\t\t\tfunc(errType error, files ...Builder) {\n\t\t\t\tExpect(afero.WriteFile(s.fs, path, []byte{}, 0o666)).To(Succeed())\n\n\t\t\t\terr := s.Execute(files...)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err).To(MatchError(errType))\n\t\t\t},\n\t\t\tEntry(\"should fail if inserting into a model that fails when a file exists and it does exist\",\n\t\t\t\tFileAlreadyExistsError{path: \"filename\"},\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: \"filename\", ifExistsAction: Error}},\n\t\t\t\tfakeInserter{fakeBuilder: fakeBuilder{path: \"filename\"}},\n\t\t\t),\n\t\t\tEntry(\"should fail if inserting into a model with unknown behavior if the file exists and it does exist\",\n\t\t\t\tUnknownIfExistsActionError{path: \"filename\", ifExistsAction: -1},\n\t\t\t\t&fakeTemplate{fakeBuilder: fakeBuilder{path: \"filename\", ifExistsAction: -1}},\n\t\t\t\tfakeInserter{fakeBuilder: fakeBuilder{path: \"filename\"}},\n\t\t\t),\n\t\t)\n\n\t\tContext(\"write when the file already exists\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\t_ = afero.WriteFile(s.fs, path, []byte{}, 0o666)\n\t\t\t})\n\n\t\t\tIt(\"should skip the file by default\", func() {\n\t\t\t\tExpect(s.Execute(&fakeTemplate{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: path},\n\t\t\t\t\tbody:        content,\n\t\t\t\t})).To(Succeed())\n\n\t\t\t\tb, err := afero.ReadFile(s.fs, path)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(BeEmpty())\n\t\t\t})\n\n\t\t\tIt(\"should write the file if configured to do so\", func() {\n\t\t\t\tExpect(s.Execute(&fakeTemplate{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile},\n\t\t\t\t\tbody:        content,\n\t\t\t\t})).To(Succeed())\n\n\t\t\t\tb, err := afero.ReadFile(s.fs, path)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(b)).To(Equal(content))\n\t\t\t})\n\n\t\t\tIt(\"should error if configured to do so\", func() {\n\t\t\t\terr := s.Execute(&fakeTemplate{\n\t\t\t\t\tfakeBuilder: fakeBuilder{path: path, ifExistsAction: Error},\n\t\t\t\t\tbody:        content,\n\t\t\t\t})\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err).To(MatchError(FileAlreadyExistsError{path: path}))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"WithConfig option\", func() {\n\t\t\tIt(\"should set repository in imports.LocalPrefix\", func() {\n\t\t\t\tcfg := cfgv3.New()\n\t\t\t\t_ = cfg.SetRepository(\"github.com/example/test\")\n\n\t\t\t\tscaffold := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithConfig(cfg))\n\n\t\t\t\tExpect(scaffold.injector.config).NotTo(BeNil())\n\t\t\t\tExpect(scaffold.injector.config.GetRepository()).To(Equal(\"github.com/example/test\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"inserter with missing file\", func() {\n\t\t\tIt(\"should skip when IgnoreFile action is used\", func() {\n\t\t\t\terr := s.Execute(\n\t\t\t\t\tfakeInserterWithIfNotExists{\n\t\t\t\t\t\tfakeInserter: fakeInserter{\n\t\t\t\t\t\t\tfakeBuilder: fakeBuilder{path: \"missing.go\"},\n\t\t\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\t\t\tNewMarkerFor(\"missing.go\", \"-\"): {\"new code\\n\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tifNotExistsAction: IgnoreFile,\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should error when ErrorIfNotExist action is used\", func() {\n\t\t\t\terr := s.Execute(\n\t\t\t\t\tfakeInserterWithIfNotExists{\n\t\t\t\t\t\tfakeInserter: fakeInserter{\n\t\t\t\t\t\t\tfakeBuilder: fakeBuilder{path: \"missing.go\"},\n\t\t\t\t\t\t\tcodeFragments: CodeFragmentsMap{\n\t\t\t\t\t\t\t\tNewMarkerFor(\"missing.go\", \"-\"): {\"new code\\n\"},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tifNotExistsAction: ErrorIfNotExist,\n\t\t\t\t\t},\n\t\t\t\t)\n\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t})\n\t\t})\n\t})\n})\n\nvar _ Builder = fakeBuilder{}\n\n// fakeBuilder is used to mock a Builder\ntype fakeBuilder struct {\n\tpath           string\n\tifExistsAction IfExistsAction\n\tTestField      string // test go template actions\n}\n\n// GetPath implements Builder\nfunc (f fakeBuilder) GetPath() string {\n\treturn f.path\n}\n\n// GetIfExistsAction implements Builder\nfunc (f fakeBuilder) GetIfExistsAction() IfExistsAction {\n\treturn f.ifExistsAction\n}\n\nvar _ RequiresValidation = fakeRequiresValidation{}\n\n// fakeRequiresValidation is used to mock a RequiresValidation in order to test Scaffold\ntype fakeRequiresValidation struct {\n\tfakeBuilder\n\n\tvalidateErr error\n}\n\n// Validate implements RequiresValidation\nfunc (f fakeRequiresValidation) Validate() error {\n\treturn f.validateErr\n}\n\nvar _ Template = &fakeTemplate{}\n\n// fakeTemplate is used to mock a File in order to test Scaffold\ntype fakeTemplate struct {\n\tfakeBuilder\n\n\tbody            string\n\terr             error\n\tparseDelimLeft  string\n\tparseDelimRight string\n}\n\nfunc (f *fakeTemplate) SetDelim(left, right string) {\n\tf.parseDelimLeft = left\n\tf.parseDelimRight = right\n}\n\nfunc (f *fakeTemplate) GetDelim() (string, string) {\n\treturn f.parseDelimLeft, f.parseDelimRight\n}\n\n// GetBody implements Template\nfunc (f *fakeTemplate) GetBody() string {\n\treturn f.body\n}\n\n// SetTemplateDefaults implements Template\nfunc (f *fakeTemplate) SetTemplateDefaults() error {\n\tif f.err != nil {\n\t\treturn f.err\n\t}\n\n\treturn nil\n}\n\ntype fakeInserter struct {\n\tfakeBuilder\n\n\tmarkers       []Marker\n\tcodeFragments CodeFragmentsMap\n}\n\n// GetMarkers implements Inserter\nfunc (f fakeInserter) GetMarkers() []Marker {\n\tif f.markers != nil {\n\t\treturn f.markers\n\t}\n\n\tmarkers := make([]Marker, 0, len(f.codeFragments))\n\tfor marker := range f.codeFragments {\n\t\tmarkers = append(markers, marker)\n\t}\n\treturn markers\n}\n\n// GetCodeFragments implements Inserter\nfunc (f fakeInserter) GetCodeFragments() CodeFragmentsMap {\n\treturn f.codeFragments\n}\n\nvar (\n\t_ Inserter             = fakeInserterWithIfNotExists{}\n\t_ HasIfNotExistsAction = fakeInserterWithIfNotExists{}\n)\n\ntype fakeInserterWithIfNotExists struct {\n\tfakeInserter\n\tifNotExistsAction IfNotExistsAction\n}\n\nfunc (f fakeInserterWithIfNotExists) GetIfNotExistsAction() IfNotExistsAction {\n\treturn f.ifNotExistsAction\n}\n"
  },
  {
    "path": "pkg/model/resource/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"fmt\"\n)\n\n// API contains information about scaffolded APIs\ntype API struct {\n\t// CRDVersion holds the CustomResourceDefinition API version used for the resource.\n\tCRDVersion string `json:\"crdVersion,omitempty\"`\n\n\t// Namespaced is true if the API is namespaced.\n\tNamespaced bool `json:\"namespaced,omitempty\"`\n}\n\n// Validate checks that the API is valid.\nfunc (api API) Validate() error {\n\t// Validate the CRD version\n\tif err := validateAPIVersion(api.CRDVersion); err != nil {\n\t\treturn fmt.Errorf(\"invalid CRD version: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Copy returns a deep copy of the API that can be safely modified without affecting the original.\nfunc (api API) Copy() API {\n\t// As this function doesn't use a pointer receiver, api is already a shallow copy.\n\t// Any field that is a pointer, slice or map needs to be deep copied.\n\treturn api\n}\n\n// Update combines fields of the APIs of two resources.\nfunc (api *API) Update(other *API) error {\n\t// If other is nil, nothing to merge\n\tif other == nil {\n\t\treturn nil\n\t}\n\n\t// Update the version.\n\tif other.CRDVersion != \"\" {\n\t\tif api.CRDVersion == \"\" {\n\t\t\tapi.CRDVersion = other.CRDVersion\n\t\t} else if api.CRDVersion != other.CRDVersion {\n\t\t\treturn fmt.Errorf(\"CRD versions do not match\")\n\t\t}\n\t}\n\n\t// Update the namespace.\n\tapi.Namespaced = api.Namespaced || other.Namespaced\n\n\treturn nil\n}\n\n// IsEmpty returns if the API's fields all contain zero-values.\nfunc (api API) IsEmpty() bool {\n\treturn api.CRDVersion == \"\" && !api.Namespaced\n}\n"
  },
  {
    "path": "pkg/model/resource/api_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\n//nolint:dupl\nvar _ = Describe(\"API\", func() {\n\tContext(\"Validate\", func() {\n\t\tIt(\"should succeed for a valid API\", func() {\n\t\t\tExpect(API{CRDVersion: v1}.Validate()).To(Succeed())\n\t\t})\n\n\t\tDescribeTable(\"should fail for invalid APIs\",\n\t\t\tfunc(api API) { Expect(api.Validate()).NotTo(Succeed()) },\n\t\t\t// Ensure that the rest of the fields are valid to check each part\n\t\t\tEntry(\"empty CRD version\", API{}),\n\t\t\tEntry(\"invalid CRD version\", API{CRDVersion: \"1\"}),\n\t\t)\n\t})\n\n\tContext(\"Update\", func() {\n\t\tvar api, other API\n\n\t\tIt(\"should do nothing if provided a nil pointer\", func() {\n\t\t\tapi = API{}\n\t\t\tExpect(api.Update(nil)).To(Succeed())\n\t\t\tExpect(api.CRDVersion).To(Equal(\"\"))\n\t\t\tExpect(api.Namespaced).To(BeFalse())\n\n\t\t\tapi = API{\n\t\t\t\tCRDVersion: v1,\n\t\t\t\tNamespaced: true,\n\t\t\t}\n\t\t\tExpect(api.Update(nil)).To(Succeed())\n\t\t\tExpect(api.CRDVersion).To(Equal(v1))\n\t\t\tExpect(api.Namespaced).To(BeTrue())\n\t\t})\n\n\t\tContext(\"CRD version\", func() {\n\t\t\tIt(\"should modify the CRD version if provided and not previously set\", func() {\n\t\t\t\tapi = API{}\n\t\t\t\tother = API{CRDVersion: v1}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.CRDVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should keep the CRD version if not provided\", func() {\n\t\t\t\tapi = API{CRDVersion: v1}\n\t\t\t\tother = API{}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.CRDVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should keep the CRD version if provided the same as previously set\", func() {\n\t\t\t\tapi = API{CRDVersion: v1}\n\t\t\t\tother = API{CRDVersion: v1}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.CRDVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should fail if previously set and provided CRD versions do not match\", func() {\n\t\t\t\tapi = API{CRDVersion: v1}\n\t\t\t\tother = API{CRDVersion: \"v1beta1\"}\n\t\t\t\tExpect(api.Update(&other)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Namespaced\", func() {\n\t\t\tIt(\"should set the namespace scope if provided and not previously set\", func() {\n\t\t\t\tapi = API{}\n\t\t\t\tother = API{Namespaced: true}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.Namespaced).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should keep the namespace scope if previously set\", func() {\n\t\t\t\tapi = API{Namespaced: true}\n\n\t\t\t\tBy(\"not providing it\")\n\t\t\t\tother = API{}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.Namespaced).To(BeTrue())\n\n\t\t\t\tBy(\"providing it\")\n\t\t\t\tother = API{Namespaced: true}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.Namespaced).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should not set the namespace scope if not provided and not previously set\", func() {\n\t\t\t\tapi = API{}\n\t\t\t\tother = API{}\n\t\t\t\tExpect(api.Update(&other)).To(Succeed())\n\t\t\t\tExpect(api.Namespaced).To(BeFalse())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"IsEmpty\", func() {\n\t\tvar (\n\t\t\tnone       API\n\t\t\tcluster    API\n\t\t\tnamespaced API\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tnone = API{}\n\t\t\tcluster = API{\n\t\t\t\tCRDVersion: v1,\n\t\t\t}\n\t\t\tnamespaced = API{\n\t\t\t\tCRDVersion: v1,\n\t\t\t\tNamespaced: true,\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return true fo an empty object\", func() {\n\t\t\tExpect(none.IsEmpty()).To(BeTrue())\n\t\t})\n\n\t\tDescribeTable(\"should return false for non-empty objects\",\n\t\t\tfunc(getAPI func() API) {\n\t\t\t\tExpect(getAPI().IsEmpty()).To(BeFalse())\n\t\t\t},\n\t\t\tEntry(\"cluster-scope\", func() API { return cluster }),\n\t\t\tEntry(\"namespace-scope\", func() API { return namespaced }),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/model/resource/gvk.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n)\n\nconst (\n\tversionInternal = \"__internal\"\n\n\tgroupRequired   = \"group cannot be empty if the domain is empty\"\n\tversionRequired = \"version cannot be empty\"\n\tkindRequired    = \"kind cannot be empty\"\n)\n\n// GVK stores the Group - Version - Kind triplet that uniquely identifies a resource.\n// In kubebuilder, the k8s fully qualified group is stored as Group and Domain to improve UX.\ntype GVK struct {\n\tGroup   string `json:\"group,omitempty\"`\n\tDomain  string `json:\"domain,omitempty\"`\n\tVersion string `json:\"version\"`\n\tKind    string `json:\"kind\"`\n}\n\n// Validate checks that the GVK is valid.\nfunc (gvk GVK) Validate() error {\n\t// Check if the qualified group has a valid DNS1123 subdomain value\n\tif gvk.QualifiedGroup() == \"\" {\n\t\treturn errors.New(groupRequired)\n\t}\n\tif err := validation.IsDNS1123Subdomain(gvk.QualifiedGroup()); err != nil {\n\t\t// NOTE: IsDNS1123Subdomain returns a slice of strings instead of an error, so no wrapping\n\t\treturn fmt.Errorf(\"either Group or Domain is invalid: %s\", err)\n\t}\n\n\t// Check if the version follows the valid pattern\n\tif gvk.Version == \"\" {\n\t\treturn errors.New(versionRequired)\n\t}\n\tif errs := validation.IsDNS1123Subdomain(gvk.Version); len(errs) > 0 && gvk.Version != versionInternal {\n\t\treturn fmt.Errorf(\"version must respect DNS-1123 (was %q)\", gvk.Version)\n\t}\n\n\t// Check if kind has a valid DNS1035 label value\n\tif gvk.Kind == \"\" {\n\t\treturn errors.New(kindRequired)\n\t}\n\tif errs := validation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errs) != 0 {\n\t\t// NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping\n\t\treturn fmt.Errorf(\"invalid Kind: %#v\", errs)\n\t}\n\n\t// Require kind to start with an uppercase character\n\t// NOTE: previous validation already fails for empty strings, gvk.Kind[0] will not panic\n\tif string(gvk.Kind[0]) == strings.ToLower(string(gvk.Kind[0])) {\n\t\treturn fmt.Errorf(\"invalid Kind: must start with an uppercase character\")\n\t}\n\n\treturn nil\n}\n\n// QualifiedGroup returns the fully qualified group name with the available information.\nfunc (gvk GVK) QualifiedGroup() string {\n\tswitch \"\" {\n\tcase gvk.Domain:\n\t\treturn gvk.Group\n\tcase gvk.Group:\n\t\treturn gvk.Domain\n\tdefault:\n\t\treturn fmt.Sprintf(\"%s.%s\", gvk.Group, gvk.Domain)\n\t}\n}\n\n// IsEqualTo compares two GVK objects.\nfunc (gvk GVK) IsEqualTo(other GVK) bool {\n\treturn gvk.Group == other.Group &&\n\t\tgvk.Domain == other.Domain &&\n\t\tgvk.Version == other.Version &&\n\t\tgvk.Kind == other.Kind\n}\n"
  },
  {
    "path": "pkg/model/resource/gvk_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"GVK\", func() {\n\tconst (\n\t\tgroup           = \"group\"\n\t\tdomain          = \"my.domain\"\n\t\tversion         = \"v1\"\n\t\tkind            = \"Kind\"\n\t\tinternalVersion = \"__internal\"\n\t)\n\n\tvar gvk GVK\n\n\tBeforeEach(func() {\n\t\tgvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind}\n\t})\n\n\tContext(\"Validate\", func() {\n\t\tDescribeTable(\"should pass valid GVKs\",\n\t\t\tfunc(get func() GVK) {\n\t\t\t\tExpect(get().Validate()).To(Succeed())\n\t\t\t},\n\t\t\tEntry(\"Standard GVK\", func() GVK { return gvk }),\n\t\t\tEntry(\"Version (__internal)\", func() GVK {\n\t\t\t\treturn GVK{Group: group, Domain: domain, Version: internalVersion, Kind: kind}\n\t\t\t}),\n\t\t)\n\n\t\tDescribeTable(\"should fail for invalid GVKs\",\n\t\t\tfunc(gvk GVK) { Expect(gvk.Validate()).NotTo(Succeed()) },\n\t\t\t// Ensure that the rest of the fields are valid to check each part\n\t\t\tEntry(\"Group (uppercase)\", GVK{Group: \"Group\", Domain: domain, Version: version, Kind: kind}),\n\t\t\tEntry(\"Group (non-alpha characters)\", GVK{Group: \"_*?\", Domain: domain, Version: version, Kind: kind}),\n\t\t\tEntry(\"Domain (uppercase)\", GVK{Group: group, Domain: \"Domain\", Version: version, Kind: kind}),\n\t\t\tEntry(\"Domain (non-alpha characters)\", GVK{Group: group, Domain: \"_*?\", Version: version, Kind: kind}),\n\t\t\tEntry(\"Group and Domain (empty)\", GVK{Group: \"\", Domain: \"\", Version: version, Kind: kind}),\n\t\t\tEntry(\"Version (empty)\", GVK{Group: group, Domain: domain, Version: \"\", Kind: kind}),\n\t\t\tEntry(\"Version (wrong prefix)\", GVK{Group: group, Domain: domain, Version: \"-example.com\", Kind: kind}),\n\t\t\tEntry(\"Version (wrong suffix)\", GVK{Group: group, Domain: domain, Version: \"example.com-\", Kind: kind}),\n\t\t\tEntry(\"Version (uppercase)\", GVK{Group: group, Domain: domain, Version: \"Example.com\", Kind: kind}),\n\t\t\tEntry(\"Version (special characters)\", GVK{Group: group, Domain: domain, Version: \"example!domain.com\", Kind: kind}),\n\t\t\tEntry(\"Version (consecutive dots)\", GVK{Group: group, Domain: domain, Version: \"example..com\", Kind: kind}),\n\t\t\tEntry(\"Kind (empty)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"\"}),\n\t\t\tEntry(\"Kind (whitespaces)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"Ki nd\"}),\n\t\t\tEntry(\"Kind (lowercase)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"kind\"}),\n\t\t\tEntry(\"Kind (starts with number)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"1Kind\"}),\n\t\t\tEntry(\"Kind (ends with `-`)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"Kind-\"}),\n\t\t\tEntry(\"Kind (non-alpha characters)\", GVK{Group: group, Domain: domain, Version: version, Kind: \"_*?\"}),\n\t\t\tEntry(\"Kind (too long)\",\n\t\t\t\tGVK{Group: group, Domain: domain, Version: version, Kind: strings.Repeat(\"a\", 64)}),\n\t\t)\n\t})\n\n\tContext(\"QualifiedGroup\", func() {\n\t\tDescribeTable(\"should return the correct string\",\n\t\t\tfunc(get func() GVK, qualifiedGroup string) {\n\t\t\t\tExpect(get().QualifiedGroup()).To(Equal(qualifiedGroup))\n\t\t\t},\n\t\t\tEntry(\"fully qualified resource\", func() GVK { return gvk }, group+\".\"+domain),\n\t\t\tEntry(\"empty group name\", func() GVK {\n\t\t\t\treturn GVK{Domain: domain, Version: version, Kind: kind}\n\t\t\t}, domain),\n\t\t\tEntry(\"empty domain\", func() GVK {\n\t\t\t\treturn GVK{Group: group, Version: version, Kind: kind}\n\t\t\t}, group),\n\t\t)\n\t})\n\n\tContext(\"IsEqualTo\", func() {\n\t\tIt(\"should return true for the same resource\", func() {\n\t\t\tExpect(gvk.IsEqualTo(GVK{Group: group, Domain: domain, Version: version, Kind: kind})).To(BeTrue())\n\t\t})\n\n\t\tDescribeTable(\"should return false for different resources\",\n\t\t\tfunc(get func() GVK) {\n\t\t\t\tExpect(gvk.IsEqualTo(get())).To(BeFalse())\n\t\t\t},\n\t\t\tEntry(\"different kind\", func() GVK {\n\t\t\t\treturn GVK{Group: group, Domain: domain, Version: version, Kind: \"Kind2\"}\n\t\t\t}),\n\t\t\tEntry(\"different version\", func() GVK {\n\t\t\t\treturn GVK{Group: group, Domain: domain, Version: \"v2\", Kind: kind}\n\t\t\t}),\n\t\t\tEntry(\"different domain\", func() GVK {\n\t\t\t\treturn GVK{Group: group, Domain: \"other.domain\", Version: version, Kind: kind}\n\t\t\t}),\n\t\t\tEntry(\"different group\", func() GVK {\n\t\t\t\treturn GVK{Group: \"group2\", Domain: domain, Version: version, Kind: kind}\n\t\t\t}),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/model/resource/resource.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n)\n\n// Resource contains the information required to scaffold files for a resource.\ntype Resource struct {\n\t// GVK contains the resource's Group-Version-Kind triplet.\n\tGVK `json:\",inline\"`\n\n\t// Plural is the resource's kind plural form.\n\tPlural string `json:\"plural,omitempty\"`\n\n\t// Path is the path to the go package where the types are defined.\n\tPath string `json:\"path,omitempty\"`\n\n\t// API holds the information related to the resource API.\n\tAPI *API `json:\"api,omitempty\"`\n\n\t// Controller specifies if a controller has been scaffolded.\n\tController bool `json:\"controller,omitempty\"`\n\n\t// Webhooks holds the information related to the associated webhooks.\n\tWebhooks *Webhooks `json:\"webhooks,omitempty\"`\n\n\t// External specifies if the resource is defined externally.\n\tExternal bool `json:\"external,omitempty\"`\n\n\t// Module specifies the Go module path for external API dependencies.\n\t// Can optionally include @version to pin the dependency (e.g., \"github.com/org/repo@v1.2.3\").\n\t// This is only used when External is true.\n\tModule string `json:\"module,omitempty\"`\n\n\t// Core specifies if the resource is from Kubernetes API.\n\tCore bool `json:\"core,omitempty\"`\n}\n\n// Validate checks that the Resource is valid.\nfunc (r Resource) Validate() error {\n\t// Validate the GVK\n\tif err := r.GVK.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\t// Validate the Plural\n\t// NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping\n\tif errors := validation.IsDNS1035Label(r.Plural); len(errors) != 0 {\n\t\treturn fmt.Errorf(\"invalid Plural: %#v\", errors)\n\t}\n\n\t// TODO: validate the path\n\n\t// Validate the API\n\tif r.API != nil && !r.API.IsEmpty() {\n\t\tif err := r.API.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid API: %w\", err)\n\t\t}\n\t}\n\n\t// Validate the Webhooks\n\tif r.Webhooks != nil && !r.Webhooks.IsEmpty() {\n\t\tif err := r.Webhooks.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid Webhooks: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PackageName returns a name valid to be used por go packages.\nfunc (r Resource) PackageName() string {\n\tif r.Group == \"\" {\n\t\treturn safeImport(r.Domain)\n\t}\n\n\treturn safeImport(r.Group)\n}\n\n// ImportAlias returns a identifier usable as an import alias for this resource.\nfunc (r Resource) ImportAlias() string {\n\tif r.Group == \"\" {\n\t\treturn safeImport(r.Domain + r.Version)\n\t}\n\n\treturn safeImport(r.Group + r.Version)\n}\n\n// HasAPI returns true if the resource has an associated API.\nfunc (r Resource) HasAPI() bool {\n\treturn r.API != nil && r.API.CRDVersion != \"\"\n}\n\n// HasController returns true if the resource has an associated controller.\nfunc (r Resource) HasController() bool {\n\treturn r.Controller\n}\n\n// HasDefaultingWebhook returns true if the resource has an associated defaulting webhook.\nfunc (r Resource) HasDefaultingWebhook() bool {\n\treturn r.Webhooks != nil && r.Webhooks.Defaulting\n}\n\n// HasValidationWebhook returns true if the resource has an associated validation webhook.\nfunc (r Resource) HasValidationWebhook() bool {\n\treturn r.Webhooks != nil && r.Webhooks.Validation\n}\n\n// HasConversionWebhook returns true if the resource has an associated conversion webhook.\nfunc (r Resource) HasConversionWebhook() bool {\n\treturn r.Webhooks != nil && r.Webhooks.Conversion\n}\n\n// IsExternal returns true if the resource was scaffold as external.\nfunc (r Resource) IsExternal() bool {\n\treturn r.External\n}\n\n// IsRegularPlural returns true if the plural is the regular plural form for the kind.\nfunc (r Resource) IsRegularPlural() bool {\n\treturn r.Plural == RegularPlural(r.Kind)\n}\n\n// Copy returns a deep copy of the Resource that can be safely modified without affecting the original.\nfunc (r Resource) Copy() Resource {\n\t// As this function doesn't use a pointer receiver, r is already a shallow copy.\n\t// Any field that is a pointer, slice or map needs to be deep copied.\n\tif r.API != nil {\n\t\tapi := r.API.Copy()\n\t\tr.API = &api\n\t}\n\tif r.Webhooks != nil {\n\t\twebhooks := r.Webhooks.Copy()\n\t\tr.Webhooks = &webhooks\n\t}\n\treturn r\n}\n\n// Update combines fields of two resources that have matching GVK favoring the receiver's values.\nfunc (r *Resource) Update(other Resource) error {\n\t// If self is nil, return an error\n\tif r == nil {\n\t\treturn fmt.Errorf(\"cannot update a nil resource\")\n\t}\n\n\t// Make sure we are not merging resources for different GVKs.\n\tif !r.IsEqualTo(other.GVK) {\n\t\treturn fmt.Errorf(\"cannot update a resource (GVK %+v) with another with non-matching GVK %+v\", r.GVK, other.GVK)\n\t}\n\n\tif r.Plural != other.Plural {\n\t\treturn fmt.Errorf(\"cannot update resource (Plural %q) with another with non-matching Plural %q\",\n\t\t\tr.Plural, other.Plural)\n\t}\n\n\tif other.Path != \"\" && r.Path != other.Path {\n\t\tif r.Path == \"\" {\n\t\t\tr.Path = other.Path\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"cannot update resource (Path %q) with another with non-matching Path %q\", r.Path, other.Path)\n\t\t}\n\t}\n\n\t// Update API.\n\tif r.API == nil && other.API != nil {\n\t\tr.API = &API{}\n\t}\n\tif err := r.API.Update(other.API); err != nil {\n\t\treturn err\n\t}\n\n\t// Update controller.\n\tr.Controller = r.Controller || other.Controller\n\n\t// Update Webhooks.\n\tif r.Webhooks == nil && other.Webhooks != nil {\n\t\tr.Webhooks = &Webhooks{}\n\t}\n\n\treturn r.Webhooks.Update(other.Webhooks)\n}\n\nfunc wrapKey(key string) string {\n\treturn fmt.Sprintf(\"%%[%s]\", key)\n}\n\n// Replacer returns a strings.Replacer that replaces resource keywords with values.\nfunc (r Resource) Replacer() *strings.Replacer {\n\treplacements := make([]string, 0, 10)\n\n\treplacements = append(replacements, wrapKey(\"group\"), r.Group)\n\treplacements = append(replacements, wrapKey(\"version\"), r.Version)\n\treplacements = append(replacements, wrapKey(\"kind\"), strings.ToLower(r.Kind))\n\treplacements = append(replacements, wrapKey(\"plural\"), strings.ToLower(r.Plural))\n\treplacements = append(replacements, wrapKey(\"package-name\"), r.PackageName())\n\n\treturn strings.NewReplacer(replacements...)\n}\n"
  },
  {
    "path": "pkg/model/resource/resource_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Resource\", func() {\n\tconst (\n\t\tgroup   = \"group\"\n\t\tdomain  = \"test.io\"\n\t\tversion = \"v1\"\n\t\tkind    = \"Kind\"\n\t\tplural  = \"kinds\"\n\t\tv1beta1 = \"v1beta1\"\n\t)\n\n\tvar (\n\t\tgvk GVK\n\t\tres Resource\n\t)\n\n\tBeforeEach(func() {\n\t\tgvk = GVK{\n\t\t\tGroup:   group,\n\t\t\tDomain:  domain,\n\t\t\tVersion: version,\n\t\t\tKind:    kind,\n\t\t}\n\t\tres = Resource{\n\t\t\tGVK:    gvk,\n\t\t\tPlural: plural,\n\t\t}\n\t})\n\n\tContext(\"Validate\", func() {\n\t\tIt(\"should succeed for a valid Resource\", func() {\n\t\t\tExpect(res.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed with empty API\", func() {\n\t\t\tExpect(Resource{GVK: gvk, Plural: plural, API: &API{}}.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed with empty Webhooks\", func() {\n\t\t\tExpect(Resource{GVK: gvk, Plural: plural, Webhooks: &Webhooks{}}.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed with nil API\", func() {\n\t\t\tExpect(Resource{GVK: gvk, Plural: plural, API: nil}.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed with nil Webhooks\", func() {\n\t\t\tExpect(Resource{GVK: gvk, Plural: plural, Webhooks: nil}.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should fail for invalid Plural with specific error\", func() {\n\t\t\tr := Resource{GVK: gvk, Plural: \"Plural\"}\n\t\t\terr := r.Validate()\n\t\t\tExpect(err).NotTo(Succeed())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"invalid Plural\"))\n\t\t})\n\n\t\tIt(\"should fail for invalid API with wrapped error\", func() {\n\t\t\tr := Resource{GVK: gvk, Plural: plural, API: &API{CRDVersion: \"1\"}}\n\t\t\terr := r.Validate()\n\t\t\tExpect(err).NotTo(Succeed())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"invalid API\"))\n\t\t})\n\n\t\tIt(\"should fail for invalid Webhooks with wrapped error\", func() {\n\t\t\tr := Resource{GVK: gvk, Plural: plural, Webhooks: &Webhooks{WebhookVersion: \"1\"}}\n\t\t\terr := r.Validate()\n\t\t\tExpect(err).NotTo(Succeed())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"invalid Webhooks\"))\n\t\t})\n\n\t\tDescribeTable(\"should fail for invalid Resources\",\n\t\t\tfunc(res Resource) { Expect(res.Validate()).NotTo(Succeed()) },\n\t\t\t// Ensure that the rest of the fields are valid to check each part\n\t\t\tEntry(\"invalid GVK\", Resource{GVK: GVK{}, Plural: \"plural\"}),\n\t\t\tEntry(\"invalid Plural\", Resource{GVK: gvk, Plural: \"Plural\"}),\n\t\t\tEntry(\"invalid API\", Resource{GVK: gvk, Plural: \"plural\", API: &API{CRDVersion: \"1\"}}),\n\t\t\tEntry(\"invalid Webhooks\", Resource{GVK: gvk, Plural: \"plural\", Webhooks: &Webhooks{WebhookVersion: \"1\"}}),\n\t\t)\n\t})\n\n\tContext(\"compound field\", func() {\n\t\tconst (\n\t\t\tsafeDomain    = \"testio\"\n\t\t\tgroupVersion  = group + version\n\t\t\tdomainVersion = safeDomain + version\n\t\t\tsafeGroup     = \"mygroup\"\n\t\t\tsafeAlias     = safeGroup + version\n\t\t)\n\n\t\tvar (\n\t\t\tresNoGroup     Resource\n\t\t\tresNoDomain    Resource\n\t\t\tresHyphenGroup Resource\n\t\t\tresDotGroup    Resource\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tresNoGroup = Resource{\n\t\t\t\tGVK: GVK{\n\t\t\t\t\t// Empty group\n\t\t\t\t\tDomain:  domain,\n\t\t\t\t\tVersion: version,\n\t\t\t\t\tKind:    kind,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresNoDomain = Resource{\n\t\t\t\tGVK: GVK{\n\t\t\t\t\tGroup: group,\n\t\t\t\t\t// Empty domain\n\t\t\t\t\tVersion: version,\n\t\t\t\t\tKind:    kind,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresHyphenGroup = Resource{\n\t\t\t\tGVK: GVK{\n\t\t\t\t\tGroup:   \"my-group\",\n\t\t\t\t\tDomain:  domain,\n\t\t\t\t\tVersion: version,\n\t\t\t\t\tKind:    kind,\n\t\t\t\t},\n\t\t\t}\n\t\t\tresDotGroup = Resource{\n\t\t\t\tGVK: GVK{\n\t\t\t\t\tGroup:   \"my.group\",\n\t\t\t\t\tDomain:  domain,\n\t\t\t\t\tVersion: version,\n\t\t\t\t\tKind:    kind,\n\t\t\t\t},\n\t\t\t}\n\t\t})\n\n\t\tDescribeTable(\"PackageName should return the correct string\",\n\t\t\tfunc(getRes func() Resource, expected string) {\n\t\t\t\tExpect(getRes().PackageName()).To(Equal(expected))\n\t\t\t},\n\t\t\tEntry(\"fully qualified resource\", func() Resource { return res }, group),\n\t\t\tEntry(\"empty group name\", func() Resource { return resNoGroup }, safeDomain),\n\t\t\tEntry(\"empty domain\", func() Resource { return resNoDomain }, group),\n\t\t\tEntry(\"hyphen-containing group\", func() Resource { return resHyphenGroup }, safeGroup),\n\t\t\tEntry(\"dot-containing group\", func() Resource { return resDotGroup }, safeGroup),\n\t\t)\n\n\t\tDescribeTable(\"ImportAlias\",\n\t\t\tfunc(getRes func() Resource, expected string) {\n\t\t\t\tExpect(getRes().ImportAlias()).To(Equal(expected))\n\t\t\t},\n\t\t\tEntry(\"fully qualified resource\", func() Resource { return res }, groupVersion),\n\t\t\tEntry(\"empty group name\", func() Resource { return resNoGroup }, domainVersion),\n\t\t\tEntry(\"empty domain\", func() Resource { return resNoDomain }, groupVersion),\n\t\t\tEntry(\"hyphen-containing group\", func() Resource { return resHyphenGroup }, safeAlias),\n\t\t\tEntry(\"dot-containing group\", func() Resource { return resDotGroup }, safeAlias),\n\t\t)\n\t})\n\n\tContext(\"part check\", func() {\n\t\tContext(\"HasAPI\", func() {\n\t\t\tIt(\"should return true if the API is scaffolded\", func() {\n\t\t\t\tExpect(Resource{API: &API{CRDVersion: \"v1\"}}.HasAPI()).To(BeTrue())\n\t\t\t})\n\n\t\t\tDescribeTable(\"should return false if the API is not scaffolded\",\n\t\t\t\tfunc(res Resource) { Expect(res.HasAPI()).To(BeFalse()) },\n\t\t\t\tEntry(\"nil API\", Resource{API: nil}),\n\t\t\t\tEntry(\"empty CRD version\", Resource{API: &API{}}),\n\t\t\t)\n\t\t})\n\n\t\tContext(\"HasController\", func() {\n\t\t\tIt(\"should return true if the controller is scaffolded\", func() {\n\t\t\t\tExpect(Resource{Controller: true}.HasController()).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should return false if the controller is not scaffolded\", func() {\n\t\t\t\tExpect(Resource{Controller: false}.HasController()).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"HasDefaultingWebhook\", func() {\n\t\t\tIt(\"should return true if the defaulting webhook is scaffolded\", func() {\n\t\t\t\tExpect(Resource{Webhooks: &Webhooks{Defaulting: true}}.HasDefaultingWebhook()).To(BeTrue())\n\t\t\t})\n\n\t\t\tDescribeTable(\"should return false if the defaulting webhook is not scaffolded\",\n\t\t\t\tfunc(res Resource) { Expect(res.HasDefaultingWebhook()).To(BeFalse()) },\n\t\t\t\tEntry(\"nil webhooks\", Resource{Webhooks: nil}),\n\t\t\t\tEntry(\"no defaulting\", Resource{Webhooks: &Webhooks{Defaulting: false}}),\n\t\t\t)\n\t\t})\n\n\t\tContext(\"HasValidationWebhook\", func() {\n\t\t\tIt(\"should return true if the validation webhook is scaffolded\", func() {\n\t\t\t\tExpect(Resource{Webhooks: &Webhooks{Validation: true}}.HasValidationWebhook()).To(BeTrue())\n\t\t\t})\n\n\t\t\tDescribeTable(\"should return false if the validation webhook is not scaffolded\",\n\t\t\t\tfunc(res Resource) { Expect(res.HasValidationWebhook()).To(BeFalse()) },\n\t\t\t\tEntry(\"nil webhooks\", Resource{Webhooks: nil}),\n\t\t\t\tEntry(\"no validation\", Resource{Webhooks: &Webhooks{Validation: false}}),\n\t\t\t)\n\t\t})\n\n\t\tContext(\"HasConversionWebhook\", func() {\n\t\t\tIt(\"should return true if the conversion webhook is scaffolded\", func() {\n\t\t\t\tExpect(Resource{Webhooks: &Webhooks{Conversion: true}}.HasConversionWebhook()).To(BeTrue())\n\t\t\t})\n\n\t\t\tDescribeTable(\"should return false if the conversion webhook is not scaffolded\",\n\t\t\t\tfunc(res Resource) { Expect(res.HasConversionWebhook()).To(BeFalse()) },\n\t\t\t\tEntry(\"nil webhooks\", Resource{Webhooks: nil}),\n\t\t\t\tEntry(\"no conversion\", Resource{Webhooks: &Webhooks{Conversion: false}}),\n\t\t\t)\n\t\t})\n\n\t\tContext(\"IsRegularPlural\", func() {\n\t\t\tIt(\"should return true if the regular plural form is used\", func() {\n\t\t\t\tExpect(res.IsRegularPlural()).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should return false if an irregular plural form is used\", func() {\n\t\t\t\tExpect(Resource{GVK: gvk, Plural: \"types\"}.IsRegularPlural()).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"IsExternal\", func() {\n\t\t\tIt(\"should return true if the resource is external\", func() {\n\t\t\t\tExpect(Resource{External: true}.IsExternal()).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should return false if the resource is not external\", func() {\n\t\t\t\tExpect(Resource{External: false}.IsExternal()).To(BeFalse())\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"Copy\", func() {\n\t\tconst (\n\t\t\tpath           = \"api/v1\"\n\t\t\tcrdVersion     = \"v1\"\n\t\t\twebhookVersion = \"v1\"\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tres = Resource{\n\t\t\t\tGVK:    gvk,\n\t\t\t\tPlural: plural,\n\t\t\t\tPath:   path,\n\t\t\t\tAPI: &API{\n\t\t\t\t\tCRDVersion: crdVersion,\n\t\t\t\t\tNamespaced: true,\n\t\t\t\t},\n\t\t\t\tController: true,\n\t\t\t\tWebhooks: &Webhooks{\n\t\t\t\t\tWebhookVersion: webhookVersion,\n\t\t\t\t\tDefaulting:     true,\n\t\t\t\t\tValidation:     true,\n\t\t\t\t\tConversion:     true,\n\t\t\t\t},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return an exact copy\", func() {\n\t\t\tother := res.Copy()\n\t\t\tExpect(other.Group).To(Equal(res.Group))\n\t\t\tExpect(other.Domain).To(Equal(res.Domain))\n\t\t\tExpect(other.Version).To(Equal(res.Version))\n\t\t\tExpect(other.Kind).To(Equal(res.Kind))\n\t\t\tExpect(other.Plural).To(Equal(res.Plural))\n\t\t\tExpect(other.Path).To(Equal(res.Path))\n\t\t\tExpect(other.API).NotTo(BeNil())\n\t\t\tExpect(other.API.CRDVersion).To(Equal(res.API.CRDVersion))\n\t\t\tExpect(other.API.Namespaced).To(Equal(res.API.Namespaced))\n\t\t\tExpect(other.Controller).To(Equal(res.Controller))\n\t\t\tExpect(other.Webhooks).NotTo(BeNil())\n\t\t\tExpect(other.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion))\n\t\t\tExpect(other.Webhooks.Defaulting).To(Equal(res.Webhooks.Defaulting))\n\t\t\tExpect(other.Webhooks.Validation).To(Equal(res.Webhooks.Validation))\n\t\t\tExpect(other.Webhooks.Conversion).To(Equal(res.Webhooks.Conversion))\n\t\t\tExpect(other.Webhooks.Spoke).To(Equal(res.Webhooks.Spoke))\n\t\t})\n\n\t\tIt(\"modifying the copy should not affect the original\", func() {\n\t\t\tother := res.Copy()\n\t\t\tother.Group = \"group2\"\n\t\t\tother.Domain = \"other.domain\"\n\t\t\tother.Version = \"v2\"\n\t\t\tother.Kind = \"kind2\"\n\t\t\tother.Plural = \"kind2s\"\n\t\t\tother.Path = \"api/v2\"\n\t\t\tother.API.CRDVersion = v1beta1\n\t\t\tother.API.Namespaced = false\n\t\t\tother.API = nil // Change fields before changing pointer\n\t\t\tother.Controller = false\n\t\t\tother.Webhooks.WebhookVersion = v1beta1\n\t\t\tother.Webhooks.Defaulting = false\n\t\t\tother.Webhooks.Validation = false\n\t\t\tother.Webhooks.Conversion = false\n\t\t\tother.Webhooks = nil // Change fields before changing pointer\n\n\t\t\tExpect(res.Group).To(Equal(group))\n\t\t\tExpect(res.Domain).To(Equal(domain))\n\t\t\tExpect(res.Version).To(Equal(version))\n\t\t\tExpect(res.Kind).To(Equal(kind))\n\t\t\tExpect(res.Plural).To(Equal(plural))\n\t\t\tExpect(res.Path).To(Equal(path))\n\t\t\tExpect(res.API).NotTo(BeNil())\n\t\t\tExpect(res.API.CRDVersion).To(Equal(crdVersion))\n\t\t\tExpect(res.API.Namespaced).To(BeTrue())\n\t\t\tExpect(res.Controller).To(BeTrue())\n\t\t\tExpect(res.Webhooks).NotTo(BeNil())\n\t\t\tExpect(res.Webhooks.WebhookVersion).To(Equal(webhookVersion))\n\t\t\tExpect(res.Webhooks.Defaulting).To(BeTrue())\n\t\t\tExpect(res.Webhooks.Validation).To(BeTrue())\n\t\t\tExpect(res.Webhooks.Conversion).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"Update\", func() {\n\t\tvar r, other Resource\n\n\t\tIt(\"should fail for nil objects\", func() {\n\t\t\tvar nilResource *Resource\n\t\t\tExpect(nilResource.Update(other)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should fail for different GVKs\", func() {\n\t\t\tr = Resource{GVK: gvk}\n\t\t\tother = Resource{\n\t\t\t\tGVK: GVK{\n\t\t\t\t\tGroup:   group,\n\t\t\t\t\tDomain:  domain,\n\t\t\t\t\tVersion: version,\n\t\t\t\t\tKind:    \"OtherKind\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tExpect(r.Update(other)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should fail for different Plurals\", func() {\n\t\t\tr = Resource{\n\t\t\t\tGVK:    gvk,\n\t\t\t\tPlural: plural,\n\t\t\t}\n\t\t\tother = Resource{\n\t\t\t\tGVK:    gvk,\n\t\t\t\tPlural: \"types\",\n\t\t\t}\n\t\t\tExpect(r.Update(other)).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should work for a new path\", func() {\n\t\t\tconst path = \"api/v1\"\n\t\t\tr = Resource{GVK: gvk}\n\t\t\tother = Resource{\n\t\t\t\tGVK:  gvk,\n\t\t\t\tPath: path,\n\t\t\t}\n\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\tExpect(r.Path).To(Equal(path))\n\t\t})\n\n\t\tIt(\"should fail for different Paths\", func() {\n\t\t\tr = Resource{\n\t\t\t\tGVK:  gvk,\n\t\t\t\tPath: \"api/v1\",\n\t\t\t}\n\t\t\tother = Resource{\n\t\t\t\tGVK:  gvk,\n\t\t\t\tPath: \"apis/group/v1\",\n\t\t\t}\n\t\t\tExpect(r.Update(other)).NotTo(Succeed())\n\t\t})\n\n\t\tContext(\"API\", func() {\n\t\t\tIt(\"should work with nil APIs\", func() {\n\t\t\t\tr = Resource{GVK: gvk}\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK: gvk,\n\t\t\t\t\tAPI: &API{CRDVersion: v1},\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.API).NotTo(BeNil())\n\t\t\t\tExpect(r.API.CRDVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should fail if API.Update fails\", func() {\n\t\t\t\tr = Resource{\n\t\t\t\t\tGVK: gvk,\n\t\t\t\t\tAPI: &API{CRDVersion: v1},\n\t\t\t\t}\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK: gvk,\n\t\t\t\t\tAPI: &API{CRDVersion: v1beta1},\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).NotTo(Succeed())\n\t\t\t})\n\n\t\t\t// The rest of the cases are tested in API.Update\n\t\t})\n\n\t\tContext(\"Controller\", func() {\n\t\t\tIt(\"should set the controller flag if provided and not previously set\", func() {\n\t\t\t\tr = Resource{GVK: gvk}\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK:        gvk,\n\t\t\t\t\tController: true,\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.Controller).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should keep the controller flag if previously set\", func() {\n\t\t\t\tr = Resource{\n\t\t\t\t\tGVK:        gvk,\n\t\t\t\t\tController: true,\n\t\t\t\t}\n\n\t\t\t\tBy(\"not providing it\")\n\t\t\t\tother = Resource{GVK: gvk}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.Controller).To(BeTrue())\n\n\t\t\t\tBy(\"providing it\")\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK:        gvk,\n\t\t\t\t\tController: true,\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.Controller).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should not set the controller flag if not provided and not previously set\", func() {\n\t\t\t\tr = Resource{GVK: gvk}\n\t\t\t\tother = Resource{GVK: gvk}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.Controller).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Webhooks\", func() {\n\t\t\tIt(\"should work with nil Webhooks\", func() {\n\t\t\t\tr = Resource{GVK: gvk}\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK:      gvk,\n\t\t\t\t\tWebhooks: &Webhooks{WebhookVersion: v1},\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).To(Succeed())\n\t\t\t\tExpect(r.Webhooks).NotTo(BeNil())\n\t\t\t\tExpect(r.Webhooks.WebhookVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should fail if Webhooks.Update fails\", func() {\n\t\t\t\tr = Resource{\n\t\t\t\t\tGVK:      gvk,\n\t\t\t\t\tWebhooks: &Webhooks{WebhookVersion: v1},\n\t\t\t\t}\n\t\t\t\tother = Resource{\n\t\t\t\t\tGVK:      gvk,\n\t\t\t\t\tWebhooks: &Webhooks{WebhookVersion: v1beta1},\n\t\t\t\t}\n\t\t\t\tExpect(r.Update(other)).NotTo(Succeed())\n\t\t\t})\n\n\t\t\t// The rest of the cases are tested in Webhooks.Update\n\t\t})\n\t})\n\n\tContext(\"Replacer\", func() {\n\t\tvar replacer *strings.Replacer\n\n\t\tBeforeEach(func() {\n\t\t\treplacer = res.Replacer()\n\t\t})\n\n\t\tDescribeTable(\"should replace the following strings\",\n\t\t\tfunc(pattern string, expected func() string) {\n\t\t\t\tExpect(replacer.Replace(pattern)).To(Equal(expected()))\n\t\t\t},\n\t\t\tEntry(\"no pattern\", \"version\", func() string { return \"version\" }),\n\t\t\tEntry(\"pattern `%[group]`\", \"%[group]\", func() string { return res.Group }),\n\t\t\tEntry(\"pattern `%[version]`\", \"%[version]\", func() string { return res.Version }),\n\t\t\tEntry(\"pattern `%[kind]`\", \"%[kind]\", func() string { return \"kind\" }),\n\t\t\tEntry(\"pattern `%[plural]`\", \"%[plural]\", func() string { return res.Plural }),\n\t\t\tEntry(\"pattern `%[package-name]`\", \"%[package-name]\", func() string { return res.PackageName() }),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/model/resource/suite_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nconst v1 = \"v1\"\n\nfunc TestResource(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Resource Suite\")\n}\n"
  },
  {
    "path": "pkg/model/resource/utils.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/flect\"\n)\n\n// validateAPIVersion validates CRD or Webhook versions\nfunc validateAPIVersion(version string) error {\n\tswitch version {\n\tcase \"v1\":\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"API version must be one of: v1beta1, v1\")\n\t}\n}\n\n// safeImport returns a cleaned version of the provided string that can be used for imports\nfunc safeImport(unsafe string) string {\n\tsafe := unsafe\n\n\t// Remove dashes and dots\n\tsafe = strings.ReplaceAll(safe, \"-\", \"\")\n\tsafe = strings.ReplaceAll(safe, \".\", \"\")\n\n\treturn safe\n}\n\n// APIPackagePath returns the default path\nfunc APIPackagePath(repo, group, version string, multiGroup bool) string {\n\tif multiGroup && group != \"\" {\n\t\treturn path.Join(repo, \"api\", group, version)\n\t}\n\treturn path.Join(repo, \"api\", version)\n}\n\n// RegularPlural returns a default plural form when none was specified\nfunc RegularPlural(singular string) string {\n\treturn flect.Pluralize(strings.ToLower(singular))\n}\n"
  },
  {
    "path": "pkg/model/resource/utils_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"path\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = DescribeTable(\"safeImport should remove unsupported characters\",\n\tfunc(unsafe, safe string) { Expect(safeImport(unsafe)).To(Equal(safe)) },\n\tEntry(\"no dots nor dashes\", \"text\", \"text\"),\n\tEntry(\"one dot\", \"my.domain\", \"mydomain\"),\n\tEntry(\"several dots\", \"example.my.domain\", \"examplemydomain\"),\n\tEntry(\"one dash\", \"example-text\", \"exampletext\"),\n\tEntry(\"several dashes\", \"other-example-text\", \"otherexampletext\"),\n\tEntry(\"both dots and dashes\", \"my-example.my.domain\", \"myexamplemydomain\"),\n)\n\nvar _ = Describe(\"APIPackagePath\", func() {\n\tconst (\n\t\trepo    = \"github.com/kubernetes-sigs/kubebuilder\"\n\t\tgroup   = \"group\"\n\t\tversion = \"v1\"\n\t)\n\n\tDescribeTable(\"should work\",\n\t\tfunc(repo, group, version string, multiGroup bool, p string) {\n\t\t\tExpect(APIPackagePath(repo, group, version, multiGroup)).To(Equal(p))\n\t\t},\n\t\tEntry(\"single group setup\", repo, group, version, false, path.Join(repo, \"api\", version)),\n\t\tEntry(\"multiple group setup\", repo, group, version, true, path.Join(repo, \"api\", group, version)),\n\t\tEntry(\"multiple group setup with empty group\", repo, \"\", version, true, path.Join(repo, \"api\", version)),\n\t)\n})\n\nvar _ = DescribeTable(\"RegularPlural should return the regular plural form\",\n\tfunc(singular, plural string) { Expect(RegularPlural(singular)).To(Equal(plural)) },\n\tEntry(\"basic singular\", \"firstmate\", \"firstmates\"),\n\tEntry(\"capitalized singular\", \"Firstmate\", \"firstmates\"),\n\tEntry(\"camel-cased singular\", \"FirstMate\", \"firstmates\"),\n\tEntry(\"irregular well-known plurals\", \"fish\", \"fish\"),\n\tEntry(\"irregular well-known plurals\", \"helmswoman\", \"helmswomen\"),\n)\n"
  },
  {
    "path": "pkg/model/resource/webhooks.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n)\n\n// Webhooks contains information about scaffolded webhooks\ntype Webhooks struct {\n\t// WebhookVersion holds the {Validating,Mutating}WebhookConfiguration API version used for the resource.\n\tWebhookVersion string `json:\"webhookVersion,omitempty\"`\n\n\t// Defaulting specifies if a defaulting webhook is associated to the resource.\n\tDefaulting bool `json:\"defaulting,omitempty\"`\n\n\t// Validation specifies if a validation webhook is associated to the resource.\n\tValidation bool `json:\"validation,omitempty\"`\n\n\t// Conversion specifies if a conversion webhook is associated to the resource.\n\tConversion bool `json:\"conversion,omitempty\"`\n\n\tSpoke []string `json:\"spoke,omitempty\"`\n\n\t// DefaultingPath holds the custom path for the defaulting/mutating webhook.\n\t// This path is used in the +kubebuilder:webhook marker annotation.\n\tDefaultingPath string `json:\"defaultingPath,omitempty\"`\n\n\t// ValidationPath holds the custom path for the validation webhook.\n\t// This path is used in the +kubebuilder:webhook marker annotation.\n\tValidationPath string `json:\"validationPath,omitempty\"`\n}\n\n// Validate checks that the Webhooks is valid.\nfunc (webhooks Webhooks) Validate() error {\n\t// Validate the Webhook version\n\tif err := validateAPIVersion(webhooks.WebhookVersion); err != nil {\n\t\treturn fmt.Errorf(\"invalid Webhook version: %w\", err)\n\t}\n\n\t// Validate that Spoke versions are unique\n\tseen := map[string]bool{}\n\tfor _, version := range webhooks.Spoke {\n\t\tif seen[version] {\n\t\t\treturn fmt.Errorf(\"duplicate spoke version: %s\", version)\n\t\t}\n\t\tseen[version] = true\n\t}\n\n\treturn nil\n}\n\n// Copy returns a deep copy of the API that can be safely modified without affecting the original.\nfunc (webhooks Webhooks) Copy() Webhooks {\n\t// Deep copy the Spoke slice\n\tvar spokeCopy []string\n\tif len(webhooks.Spoke) > 0 {\n\t\tspokeCopy = make([]string, len(webhooks.Spoke))\n\t\tcopy(spokeCopy, webhooks.Spoke)\n\t} else {\n\t\tspokeCopy = nil\n\t}\n\n\treturn Webhooks{\n\t\tWebhookVersion: webhooks.WebhookVersion,\n\t\tDefaulting:     webhooks.Defaulting,\n\t\tValidation:     webhooks.Validation,\n\t\tConversion:     webhooks.Conversion,\n\t\tSpoke:          spokeCopy,\n\t\tDefaultingPath: webhooks.DefaultingPath,\n\t\tValidationPath: webhooks.ValidationPath,\n\t}\n}\n\n// Update combines fields of the webhooks of two resources.\nfunc (webhooks *Webhooks) Update(other *Webhooks) error {\n\t// If other is nil, nothing to merge\n\tif other == nil {\n\t\treturn nil\n\t}\n\n\t// Update the version.\n\tif other.WebhookVersion != \"\" {\n\t\tif webhooks.WebhookVersion == \"\" {\n\t\t\twebhooks.WebhookVersion = other.WebhookVersion\n\t\t} else if webhooks.WebhookVersion != other.WebhookVersion {\n\t\t\treturn fmt.Errorf(\"webhook versions do not match\")\n\t\t}\n\t}\n\n\t// Update defaulting.\n\twebhooks.Defaulting = webhooks.Defaulting || other.Defaulting\n\n\t// Update validation.\n\twebhooks.Validation = webhooks.Validation || other.Validation\n\n\t// Update conversion.\n\twebhooks.Conversion = webhooks.Conversion || other.Conversion\n\n\t// Update Spoke (merge without duplicates)\n\tif len(other.Spoke) > 0 {\n\t\texistingSpokes := make(map[string]struct{})\n\t\tfor _, spoke := range webhooks.Spoke {\n\t\t\texistingSpokes[spoke] = struct{}{}\n\t\t}\n\t\tfor _, spoke := range other.Spoke {\n\t\t\tif _, exists := existingSpokes[spoke]; !exists {\n\t\t\t\twebhooks.Spoke = append(webhooks.Spoke, spoke)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Update custom paths (other takes precedence if not empty)\n\tif other.DefaultingPath != \"\" {\n\t\twebhooks.DefaultingPath = other.DefaultingPath\n\t}\n\tif other.ValidationPath != \"\" {\n\t\twebhooks.ValidationPath = other.ValidationPath\n\t}\n\n\treturn nil\n}\n\n// IsEmpty returns if the Webhooks' fields all contain zero-values.\nfunc (webhooks Webhooks) IsEmpty() bool {\n\treturn webhooks.WebhookVersion == \"\" &&\n\t\t!webhooks.Defaulting && !webhooks.Validation &&\n\t\t!webhooks.Conversion && len(webhooks.Spoke) == 0 &&\n\t\twebhooks.DefaultingPath == \"\" && webhooks.ValidationPath == \"\"\n}\n\n// AddSpoke adds a new spoke version to the Webhooks configuration.\nfunc (webhooks *Webhooks) AddSpoke(version string) {\n\t// Ensure the version is not already present\n\tif slices.Contains(webhooks.Spoke, version) {\n\t\treturn\n\t}\n\twebhooks.Spoke = append(webhooks.Spoke, version)\n}\n"
  },
  {
    "path": "pkg/model/resource/webhooks_copy_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Webhooks Copy and AddSpoke\", func() {\n\tContext(\"Copy\", func() {\n\t\tIt(\"should create a deep copy of Webhooks\", func() {\n\t\t\toriginal := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     false,\n\t\t\t\tSpoke:          []string{\"v1\", \"v2\"},\n\t\t\t}\n\n\t\t\tcopied := original.Copy()\n\n\t\t\tExpect(copied.WebhookVersion).To(Equal(original.WebhookVersion))\n\t\t\tExpect(copied.Defaulting).To(Equal(original.Defaulting))\n\t\t\tExpect(copied.Validation).To(Equal(original.Validation))\n\t\t\tExpect(copied.Conversion).To(Equal(original.Conversion))\n\t\t\tExpect(copied.Spoke).To(Equal(original.Spoke))\n\t\t})\n\n\t\tIt(\"should not affect original when modifying the copy\", func() {\n\t\t\toriginal := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tDefaulting:     true,\n\t\t\t\tSpoke:          []string{\"v1\"},\n\t\t\t}\n\n\t\t\tcopied := original.Copy()\n\t\t\tcopied.Defaulting = false\n\t\t\tcopied.Spoke = append(copied.Spoke, \"v2\")\n\n\t\t\tExpect(original.Defaulting).To(BeTrue())\n\t\t\tExpect(original.Spoke).To(Equal([]string{\"v1\"}))\n\t\t\tExpect(copied.Defaulting).To(BeFalse())\n\t\t\tExpect(copied.Spoke).To(Equal([]string{\"v1\", \"v2\"}))\n\t\t})\n\n\t\tIt(\"should handle empty Spoke slice\", func() {\n\t\t\toriginal := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tSpoke:          []string{},\n\t\t\t}\n\n\t\t\tcopied := original.Copy()\n\t\t\tExpect(copied.Spoke).To(BeNil())\n\t\t})\n\n\t\tIt(\"should handle nil Spoke slice\", func() {\n\t\t\toriginal := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tSpoke:          nil,\n\t\t\t}\n\n\t\t\tcopied := original.Copy()\n\t\t\tExpect(copied.Spoke).To(BeNil())\n\t\t})\n\n\t\tIt(\"should create independent Spoke slices\", func() {\n\t\t\toriginal := Webhooks{\n\t\t\t\tSpoke: []string{\"v1\"},\n\t\t\t}\n\n\t\t\tcopied := original.Copy()\n\t\t\tcopied.Spoke[0] = \"v2\"\n\n\t\t\tExpect(original.Spoke[0]).To(Equal(\"v1\"))\n\t\t\tExpect(copied.Spoke[0]).To(Equal(\"v2\"))\n\t\t})\n\t})\n\n\tContext(\"AddSpoke\", func() {\n\t\tIt(\"should add a new spoke version\", func() {\n\t\t\twebhook := &Webhooks{}\n\t\t\twebhook.AddSpoke(\"v1\")\n\n\t\t\tExpect(webhook.Spoke).To(HaveLen(1))\n\t\t\tExpect(webhook.Spoke).To(ContainElement(\"v1\"))\n\t\t})\n\n\t\tIt(\"should not add duplicate spoke versions\", func() {\n\t\t\twebhook := &Webhooks{\n\t\t\t\tSpoke: []string{\"v1\"},\n\t\t\t}\n\t\t\twebhook.AddSpoke(\"v1\")\n\n\t\t\tExpect(webhook.Spoke).To(HaveLen(1))\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v1\"}))\n\t\t})\n\n\t\tIt(\"should add multiple different spoke versions\", func() {\n\t\t\twebhook := &Webhooks{}\n\t\t\twebhook.AddSpoke(\"v1\")\n\t\t\twebhook.AddSpoke(\"v2\")\n\t\t\twebhook.AddSpoke(\"v3\")\n\n\t\t\tExpect(webhook.Spoke).To(HaveLen(3))\n\t\t\tExpect(webhook.Spoke).To(ContainElements(\"v1\", \"v2\", \"v3\"))\n\t\t})\n\n\t\tIt(\"should handle adding existing version in the middle\", func() {\n\t\t\twebhook := &Webhooks{\n\t\t\t\tSpoke: []string{\"v1\", \"v2\", \"v3\"},\n\t\t\t}\n\t\t\twebhook.AddSpoke(\"v2\")\n\n\t\t\tExpect(webhook.Spoke).To(HaveLen(3))\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v1\", \"v2\", \"v3\"}))\n\t\t})\n\t})\n\n\tContext(\"Validate with duplicate Spoke versions\", func() {\n\t\tIt(\"should fail validation with duplicate spoke versions\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tSpoke:          []string{\"v1\", \"v1\"},\n\t\t\t}\n\n\t\t\tExpect(webhook.Validate()).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed validation with unique spoke versions\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tSpoke:          []string{\"v1\", \"v2\", \"v3\"},\n\t\t\t}\n\n\t\t\tExpect(webhook.Validate()).To(Succeed())\n\t\t})\n\t})\n\n\tContext(\"IsEmpty with Spoke\", func() {\n\t\tIt(\"should return false when only Spoke is set\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tSpoke: []string{\"v1\"},\n\t\t\t}\n\n\t\t\tExpect(webhook.IsEmpty()).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return true when Spoke is empty array\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tSpoke: []string{},\n\t\t\t}\n\n\t\t\tExpect(webhook.IsEmpty()).To(BeTrue())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/model/resource/webhooks_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage resource\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\n//nolint:dupl\nvar _ = Describe(\"Webhooks\", func() {\n\tContext(\"Validate\", func() {\n\t\tIt(\"should succeed for a valid Webhooks\", func() {\n\t\t\tExpect(Webhooks{WebhookVersion: v1}.Validate()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should succeed for valid Webhooks with unique spoke versions\", func() {\n\t\t\tExpect(Webhooks{WebhookVersion: v1, Spoke: []string{\"v1\", \"v2\", \"v3\"}}.Validate()).To(Succeed())\n\t\t})\n\n\t\tDescribeTable(\"should fail for invalid Webhooks\",\n\t\t\tfunc(webhooks Webhooks) { Expect(webhooks.Validate()).NotTo(Succeed()) },\n\t\t\t// Ensure that the rest of the fields are valid to check each part\n\t\t\tEntry(\"empty webhook version\", Webhooks{}),\n\t\t\tEntry(\"invalid webhook version\", Webhooks{WebhookVersion: \"1\"}),\n\t\t\tEntry(\"duplicate spoke versions\", Webhooks{WebhookVersion: v1, Spoke: []string{\"v1\", \"v2\", \"v1\"}}),\n\t\t)\n\t})\n\n\tContext(\"Update\", func() {\n\t\tvar webhook, other Webhooks\n\n\t\tIt(\"should do nothing if provided a nil pointer\", func() {\n\t\t\twebhook = Webhooks{}\n\t\t\tExpect(webhook.Update(nil)).To(Succeed())\n\t\t\tExpect(webhook.WebhookVersion).To(Equal(\"\"))\n\t\t\tExpect(webhook.Defaulting).To(BeFalse())\n\t\t\tExpect(webhook.Validation).To(BeFalse())\n\t\t\tExpect(webhook.Conversion).To(BeFalse())\n\n\t\t\twebhook = Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     true,\n\t\t\t\tSpoke:          []string{\"v2\"},\n\t\t\t}\n\t\t\tExpect(webhook.Update(nil)).To(Succeed())\n\t\t\tExpect(webhook.WebhookVersion).To(Equal(v1))\n\t\t\tExpect(webhook.Defaulting).To(BeTrue())\n\t\t\tExpect(webhook.Validation).To(BeTrue())\n\t\t\tExpect(webhook.Conversion).To(BeTrue())\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v2\"}))\n\t\t})\n\n\t\tIt(\"should merge Spoke values without duplicates\", func() {\n\t\t\twebhook = Webhooks{\n\t\t\t\tSpoke: []string{\"v1\"},\n\t\t\t}\n\t\t\tother = Webhooks{\n\t\t\t\tSpoke: []string{\"v1\", \"v2\"},\n\t\t\t}\n\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\tExpect(webhook.Spoke).To(ConsistOf(\"v1\", \"v2\")) // Ensure no duplicates\n\t\t})\n\n\t\tContext(\"webhooks version\", func() {\n\t\t\tIt(\"should modify the webhooks version if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{WebhookVersion: v1}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.WebhookVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should keep the webhooks version if not provided\", func() {\n\t\t\t\twebhook = Webhooks{WebhookVersion: v1}\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.WebhookVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should keep the webhooks version if provided the same as previously set\", func() {\n\t\t\t\twebhook = Webhooks{WebhookVersion: v1}\n\t\t\t\tother = Webhooks{WebhookVersion: v1}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.WebhookVersion).To(Equal(v1))\n\t\t\t})\n\n\t\t\tIt(\"should fail if previously set and provided webhooks versions do not match\", func() {\n\t\t\t\twebhook = Webhooks{WebhookVersion: v1}\n\t\t\t\tother = Webhooks{WebhookVersion: \"v1beta1\"}\n\t\t\t\tExpect(webhook.Update(&other)).NotTo(Succeed())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Defaulting\", func() {\n\t\t\tIt(\"should set the defaulting webhook if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{Defaulting: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Defaulting).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should keep the defaulting webhook if previously set\", func() {\n\t\t\t\twebhook = Webhooks{Defaulting: true}\n\n\t\t\t\tBy(\"not providing it\")\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Defaulting).To(BeTrue())\n\n\t\t\t\tBy(\"providing it\")\n\t\t\t\tother = Webhooks{Defaulting: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Defaulting).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should not set the defaulting webhook if not provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Defaulting).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Validation\", func() {\n\t\t\tIt(\"should set the validation webhook if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{Validation: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Validation).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should keep the validation webhook if previously set\", func() {\n\t\t\t\twebhook = Webhooks{Validation: true}\n\n\t\t\t\tBy(\"not providing it\")\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Validation).To(BeTrue())\n\n\t\t\t\tBy(\"providing it\")\n\t\t\t\tother = Webhooks{Validation: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Validation).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should not set the validation webhook if not provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Validation).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Conversion\", func() {\n\t\t\tIt(\"should set the conversion webhook if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{Conversion: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Conversion).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should keep the conversion webhook if previously set\", func() {\n\t\t\t\twebhook = Webhooks{Conversion: true}\n\n\t\t\t\tBy(\"not providing it\")\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Conversion).To(BeTrue())\n\n\t\t\t\tBy(\"providing it\")\n\t\t\t\tother = Webhooks{Conversion: true}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Conversion).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should not set the conversion webhook if not provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.Conversion).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Custom webhook paths\", func() {\n\t\t\tIt(\"should set the defaulting path if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{DefaultingPath: \"/custom-defaulting\"}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.DefaultingPath).To(Equal(\"/custom-defaulting\"))\n\t\t\t})\n\n\t\t\tIt(\"should update the defaulting path if other provides a new one\", func() {\n\t\t\t\twebhook = Webhooks{DefaultingPath: \"/old-path\"}\n\t\t\t\tother = Webhooks{DefaultingPath: \"/new-path\"}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.DefaultingPath).To(Equal(\"/new-path\"))\n\t\t\t})\n\n\t\t\tIt(\"should set the validation path if provided and not previously set\", func() {\n\t\t\t\twebhook = Webhooks{}\n\t\t\t\tother = Webhooks{ValidationPath: \"/custom-validation\"}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.ValidationPath).To(Equal(\"/custom-validation\"))\n\t\t\t})\n\n\t\t\tIt(\"should update the validation path if other provides a new one\", func() {\n\t\t\t\twebhook = Webhooks{ValidationPath: \"/old-path\"}\n\t\t\t\tother = Webhooks{ValidationPath: \"/new-path\"}\n\t\t\t\tExpect(webhook.Update(&other)).To(Succeed())\n\t\t\t\tExpect(webhook.ValidationPath).To(Equal(\"/new-path\"))\n\t\t\t})\n\t\t})\n\t})\n\n\tContext(\"IsEmpty\", func() {\n\t\tvar (\n\t\t\tnone       Webhooks\n\t\t\tdefaulting Webhooks\n\t\t\tvalidation Webhooks\n\t\t\tconversion Webhooks\n\n\t\t\tdefaultingAndValidation Webhooks\n\t\t\tdefaultingAndConversion Webhooks\n\t\t\tvalidationAndConversion Webhooks\n\n\t\t\tall Webhooks\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tnone = Webhooks{}\n\t\t\tdefaulting = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     false,\n\t\t\t\tConversion:     false,\n\t\t\t}\n\t\t\tvalidation = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     false,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     false,\n\t\t\t}\n\t\t\tconversion = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     false,\n\t\t\t\tValidation:     false,\n\t\t\t\tConversion:     true,\n\t\t\t}\n\t\t\tdefaultingAndValidation = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     false,\n\t\t\t}\n\t\t\tdefaultingAndConversion = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     false,\n\t\t\t\tConversion:     true,\n\t\t\t}\n\t\t\tvalidationAndConversion = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     false,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     true,\n\t\t\t}\n\t\t\tall = Webhooks{\n\t\t\t\tWebhookVersion: \"v1\",\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     true,\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return true fo an empty object\", func() {\n\t\t\tExpect(none.IsEmpty()).To(BeTrue())\n\t\t})\n\n\t\tDescribeTable(\"should return false for non-empty objects\",\n\t\t\tfunc(get func() Webhooks) {\n\t\t\t\tExpect(get().IsEmpty()).To(BeFalse())\n\t\t\t},\n\t\t\tEntry(\"defaulting\", func() Webhooks { return defaulting }),\n\t\t\tEntry(\"validation\", func() Webhooks { return validation }),\n\t\t\tEntry(\"conversion\", func() Webhooks { return conversion }),\n\t\t\tEntry(\"defaulting and validation\", func() Webhooks { return defaultingAndValidation }),\n\t\t\tEntry(\"defaulting and conversion\", func() Webhooks { return defaultingAndConversion }),\n\t\t\tEntry(\"validation and conversion\", func() Webhooks { return validationAndConversion }),\n\t\t\tEntry(\"defaulting and validation and conversion\", func() Webhooks { return all }),\n\t\t)\n\t})\n\n\tContext(\"AddSpoke\", func() {\n\t\tIt(\"should add a spoke version if not already present\", func() {\n\t\t\twebhook := Webhooks{}\n\t\t\twebhook.AddSpoke(\"v1\")\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v1\"}))\n\n\t\t\twebhook.AddSpoke(\"v2\")\n\t\t\tExpect(webhook.Spoke).To(ConsistOf(\"v1\", \"v2\"))\n\t\t})\n\n\t\tIt(\"should not add a duplicate spoke version\", func() {\n\t\t\twebhook := Webhooks{Spoke: []string{\"v1\"}}\n\t\t\twebhook.AddSpoke(\"v1\")\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v1\"}))\n\t\t})\n\t})\n\n\tContext(\"Copy\", func() {\n\t\tIt(\"should return an exact copy\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     true,\n\t\t\t\tSpoke:          []string{\"v1\", \"v2\"},\n\t\t\t\tDefaultingPath: \"/custom-defaulting\",\n\t\t\t\tValidationPath: \"/custom-validation\",\n\t\t\t}\n\t\t\tother := webhook.Copy()\n\n\t\t\tExpect(other.WebhookVersion).To(Equal(webhook.WebhookVersion))\n\t\t\tExpect(other.Defaulting).To(Equal(webhook.Defaulting))\n\t\t\tExpect(other.Validation).To(Equal(webhook.Validation))\n\t\t\tExpect(other.Conversion).To(Equal(webhook.Conversion))\n\t\t\tExpect(other.Spoke).To(Equal(webhook.Spoke))\n\t\t\tExpect(other.DefaultingPath).To(Equal(webhook.DefaultingPath))\n\t\t\tExpect(other.ValidationPath).To(Equal(webhook.ValidationPath))\n\t\t})\n\n\t\tIt(\"modifying the copy should not affect the original\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tDefaulting:     true,\n\t\t\t\tValidation:     true,\n\t\t\t\tConversion:     true,\n\t\t\t\tSpoke:          []string{\"v1\", \"v2\"},\n\t\t\t\tDefaultingPath: \"/custom-defaulting\",\n\t\t\t\tValidationPath: \"/custom-validation\",\n\t\t\t}\n\t\t\tother := webhook.Copy()\n\n\t\t\t// Modify the copy\n\t\t\tother.WebhookVersion = \"v1beta1\"\n\t\t\tother.Defaulting = false\n\t\t\tother.Validation = false\n\t\t\tother.Conversion = false\n\t\t\tother.Spoke[0] = \"v3\"\n\t\t\tother.Spoke = append(other.Spoke, \"v4\")\n\t\t\tother.DefaultingPath = \"/new-defaulting\"\n\t\t\tother.ValidationPath = \"/new-validation\"\n\n\t\t\t// Original should remain unchanged\n\t\t\tExpect(webhook.WebhookVersion).To(Equal(v1))\n\t\t\tExpect(webhook.Defaulting).To(BeTrue())\n\t\t\tExpect(webhook.Validation).To(BeTrue())\n\t\t\tExpect(webhook.Conversion).To(BeTrue())\n\t\t\tExpect(webhook.Spoke).To(Equal([]string{\"v1\", \"v2\"}))\n\t\t\tExpect(webhook.DefaultingPath).To(Equal(\"/custom-defaulting\"))\n\t\t\tExpect(webhook.ValidationPath).To(Equal(\"/custom-validation\"))\n\t\t})\n\n\t\tIt(\"should handle nil Spoke slice\", func() {\n\t\t\twebhook := Webhooks{\n\t\t\t\tWebhookVersion: v1,\n\t\t\t\tSpoke:          nil,\n\t\t\t}\n\t\t\tother := webhook.Copy()\n\n\t\t\tExpect(other.Spoke).To(BeNil())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/model/stage/stage.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stage\n\nimport (\n\t\"errors\"\n)\n\nvar errInvalid = errors.New(\"invalid version stage\")\n\n// Stage represents the stability of a version\ntype Stage uint8\n\n// Order Stage in decreasing degree of stability for comparison purposes.\n// Stable must be 0 so that it is the default Stage\nconst ( // The order in this const declaration will be used to order version stages except for Stable\n\t// Stable should be used for plugins that are rarely changed in backwards-compatible ways, e.g. bug fixes.\n\tStable Stage = iota\n\t// Beta should be used for plugins that may be changed in minor ways and are not expected to break between uses.\n\tBeta Stage = iota\n\t// Alpha should be used for plugins that are frequently changed and may break between uses.\n\tAlpha Stage = iota\n)\n\nconst (\n\talpha  = \"alpha\"\n\tbeta   = \"beta\"\n\tstable = \"\"\n)\n\n// ParseStage parses stage into a Stage, assuming it is one of the valid stages\nfunc ParseStage(stage string) (Stage, error) {\n\tvar s Stage\n\treturn s, s.Parse(stage)\n}\n\n// Parse parses stage inline, assuming it is one of the valid stages\nfunc (s *Stage) Parse(stage string) error {\n\tswitch stage {\n\tcase alpha:\n\t\t*s = Alpha\n\tcase beta:\n\t\t*s = Beta\n\tcase stable:\n\t\t*s = Stable\n\tdefault:\n\t\treturn errInvalid\n\t}\n\treturn nil\n}\n\n// String returns the string representation of s\nfunc (s Stage) String() string {\n\tswitch s {\n\tcase Alpha:\n\t\treturn alpha\n\tcase Beta:\n\t\treturn beta\n\tcase Stable:\n\t\treturn stable\n\tdefault:\n\t\tpanic(errInvalid)\n\t}\n}\n\n// Validate ensures that the stage is one of the valid stages\nfunc (s Stage) Validate() error {\n\tswitch s {\n\tcase Alpha:\n\tcase Beta:\n\tcase Stable:\n\tdefault:\n\t\treturn errInvalid\n\t}\n\n\treturn nil\n}\n\n// Compare returns -1 if s < other, 0 if s == other, and 1 if s > other.\nfunc (s Stage) Compare(other Stage) int {\n\tif s == other {\n\t\treturn 0\n\t}\n\n\t// Stage are sorted in decreasing order\n\tif s > other {\n\t\treturn -1\n\t}\n\treturn 1\n}\n\n// IsStable returns whether the stage is stable or not\nfunc (s Stage) IsStable() bool {\n\treturn s == Stable\n}\n"
  },
  {
    "path": "pkg/model/stage/stage_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage stage\n\nimport (\n\t\"slices\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // An alias is required because Context is defined elsewhere in this package.\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestStage(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Stage Suite\")\n}\n\nvar _ = Describe(\"ParseStage\", func() {\n\tDescribeTable(\"should be correctly parsed for valid stage strings\",\n\t\tfunc(str string, stage Stage) {\n\t\t\ts, err := ParseStage(str)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(s).To(Equal(stage))\n\t\t},\n\t\tEntry(\"for alpha stage\", \"alpha\", Alpha),\n\t\tEntry(\"for beta stage\", \"beta\", Beta),\n\t\tEntry(\"for stable stage\", \"\", Stable),\n\t)\n\n\tDescribeTable(\"should error when parsing invalid stage strings\",\n\t\tfunc(str string) {\n\t\t\t_, err := ParseStage(str)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t},\n\t\tEntry(\"passing a number as the stage string\", \"1\"),\n\t\tEntry(\"passing `gamma` as the stage string\", \"gamma\"),\n\t\tEntry(\"passing a dash-prefixed stage string\", \"-alpha\"),\n\t)\n})\n\nvar _ = Describe(\"Stage\", func() {\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the correct string value\",\n\t\t\tfunc(stage Stage, str string) { Expect(stage.String()).To(Equal(str)) },\n\t\t\tEntry(\"for alpha stage\", Alpha, \"alpha\"),\n\t\t\tEntry(\"for beta stage\", Beta, \"beta\"),\n\t\t\tEntry(\"for stable stage\", Stable, \"\"),\n\t\t)\n\n\t\tDescribeTable(\"should panic\",\n\t\t\tfunc(stage Stage) { Expect(func() { _ = stage.String() }).To(Panic()) },\n\t\t\tEntry(\"for stage 34\", Stage(34)),\n\t\t\tEntry(\"for stage 75\", Stage(75)),\n\t\t\tEntry(\"for stage 123\", Stage(123)),\n\t\t\tEntry(\"for stage 255\", Stage(255)),\n\t\t)\n\t})\n\n\tContext(\"Validate\", func() {\n\t\tDescribeTable(\"should validate existing stages\",\n\t\t\tfunc(stage Stage) { Expect(stage.Validate()).To(Succeed()) },\n\t\t\tEntry(\"for alpha stage\", Alpha),\n\t\t\tEntry(\"for beta stage\", Beta),\n\t\t\tEntry(\"for stable stage\", Stable),\n\t\t)\n\n\t\tDescribeTable(\"should fail for non-existing stages\",\n\t\t\tfunc(stage Stage) { Expect(stage.Validate()).NotTo(Succeed()) },\n\t\t\tEntry(\"for stage 34\", Stage(34)),\n\t\t\tEntry(\"for stage 75\", Stage(75)),\n\t\t\tEntry(\"for stage 123\", Stage(123)),\n\t\t\tEntry(\"for stage 255\", Stage(255)),\n\t\t)\n\t})\n\n\tContext(\"Compare\", func() {\n\t\t// Test Stage.Compare by sorting a list\n\t\tvar (\n\t\t\tstages       []Stage\n\t\t\tsortedStages []Stage\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tstages = []Stage{\n\t\t\t\tStable,\n\t\t\t\tAlpha,\n\t\t\t\tStable,\n\t\t\t\tBeta,\n\t\t\t\tBeta,\n\t\t\t\tAlpha,\n\t\t\t}\n\n\t\t\tsortedStages = []Stage{\n\t\t\t\tAlpha,\n\t\t\t\tAlpha,\n\t\t\t\tBeta,\n\t\t\t\tBeta,\n\t\t\t\tStable,\n\t\t\t\tStable,\n\t\t\t}\n\t\t})\n\n\t\tIt(\"sorts stages correctly\", func() {\n\t\t\tslices.SortStableFunc(stages, func(a, b Stage) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(stages).To(Equal(sortedStages))\n\t\t})\n\t})\n\n\tContext(\"IsStable\", func() {\n\t\tIt(\"should return true for stable stage\", func() {\n\t\t\tExpect(Stable.IsStable()).To(BeTrue())\n\t\t})\n\n\t\tDescribeTable(\"should return false for any unstable stage\",\n\t\t\tfunc(stage Stage) { Expect(stage.IsStable()).To(BeFalse()) },\n\t\t\tEntry(\"for alpha stage\", Alpha),\n\t\t\tEntry(\"for beta stage\", Beta),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/bundle.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\ntype bundle struct {\n\tname        string\n\tversion     Version\n\tplugins     []Plugin\n\tdescription string\n\n\tsupportedProjectVersions []config.Version\n\tdeprecateWarning         string\n}\n\n// BundleOption define the options to create the bundle\ntype BundleOption func(*bundle)\n\n// WithName allow set the name of the Bundle Plugin\nfunc WithName(name string) BundleOption {\n\treturn func(opts *bundle) {\n\t\topts.name = name\n\t}\n}\n\n// WithVersion allow set the version of the Bundle Plugin\nfunc WithVersion(version Version) BundleOption {\n\treturn func(opts *bundle) {\n\t\topts.version = version\n\t}\n}\n\n// WithPlugins allow set the plugins which will be used in the composition for the Bundle Plugin\nfunc WithPlugins(plugins ...Plugin) BundleOption {\n\treturn func(opts *bundle) {\n\t\topts.plugins = plugins\n\t}\n}\n\n// WithDeprecationMessage allow set a deprecate message when needed\nfunc WithDeprecationMessage(msg string) BundleOption {\n\treturn func(opts *bundle) {\n\t\topts.deprecateWarning = msg\n\t}\n}\n\n// WithDescription allows setting a description for the bundle\nfunc WithDescription(desc string) BundleOption {\n\treturn func(opts *bundle) {\n\t\topts.description = desc\n\t}\n}\n\n// NewBundleWithOptions creates a new Bundle with the provided BundleOptions.\n// The list of supported project versions is computed from the provided plugins in options.\nfunc NewBundleWithOptions(opts ...BundleOption) (Bundle, error) {\n\tbundleOpts := bundle{}\n\n\tfor _, opts := range opts {\n\t\topts(&bundleOpts)\n\t}\n\n\tsupportedProjectVersions := CommonSupportedProjectVersions(bundleOpts.plugins...)\n\tif len(supportedProjectVersions) == 0 {\n\t\treturn nil, fmt.Errorf(\"in order to bundle plugins, they must all support at least one common project version\")\n\t}\n\n\t// Plugins may be bundles themselves, so unbundle here\n\t// NOTE(Adirio): unbundling here ensures that Bundle.Plugin always returns a flat list of Plugins instead of also\n\t//               including Bundles, and therefore we don't have to use a recursive algorithm when resolving.\n\tallPlugins := make([]Plugin, 0, len(bundleOpts.plugins))\n\tfor _, plugin := range bundleOpts.plugins {\n\t\tif pluginBundle, isBundle := plugin.(Bundle); isBundle {\n\t\t\tallPlugins = append(allPlugins, pluginBundle.Plugins()...)\n\t\t} else {\n\t\t\tallPlugins = append(allPlugins, plugin)\n\t\t}\n\t}\n\n\treturn bundle{\n\t\tname:                     bundleOpts.name,\n\t\tversion:                  bundleOpts.version,\n\t\tplugins:                  allPlugins,\n\t\tdescription:              bundleOpts.description,\n\t\tsupportedProjectVersions: supportedProjectVersions,\n\t\tdeprecateWarning:         bundleOpts.deprecateWarning,\n\t}, nil\n}\n\n// Name implements Plugin\nfunc (b bundle) Name() string {\n\treturn b.name\n}\n\n// Version implements Plugin\nfunc (b bundle) Version() Version {\n\treturn b.version\n}\n\n// SupportedProjectVersions implements Plugin\nfunc (b bundle) SupportedProjectVersions() []config.Version {\n\treturn b.supportedProjectVersions\n}\n\n// Plugins implements Bundle\nfunc (b bundle) Plugins() []Plugin {\n\treturn b.plugins\n}\n\n// Description implements Describable\nfunc (b bundle) Description() string {\n\treturn b.description\n}\n\n// DeprecationWarning return the warning message\nfunc (b bundle) DeprecationWarning() string {\n\treturn b.deprecateWarning\n}\n"
  },
  {
    "path": "pkg/plugin/bundle_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"slices\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nvar _ = Describe(\"Bundle\", func() {\n\tconst (\n\t\tname = \"bundle.kubebuilder.io\"\n\t)\n\n\tvar (\n\t\tv Version\n\n\t\tp1 mockPlugin\n\t\tp2 mockPlugin\n\t\tp3 mockPlugin\n\t\tp4 mockPlugin\n\t)\n\n\tBeforeEach(func() {\n\t\tv = Version{Number: 1}\n\n\t\tp1 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t{Number: 1},\n\t\t\t{Number: 2},\n\t\t\t{Number: 3},\n\t\t}}\n\t\tp2 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t{Number: 1},\n\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t{Number: 3, Stage: stage.Alpha},\n\t\t}}\n\t\tp3 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t{Number: 1},\n\t\t\t{Number: 2},\n\t\t\t{Number: 3, Stage: stage.Beta},\n\t\t}}\n\t\tp4 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t{Number: 2},\n\t\t\t{Number: 3},\n\t\t}}\n\t})\n\n\tContext(\"NewBundle\", func() {\n\t\tIt(\"should succeed for plugins with common supported project versions\", func() {\n\t\t\tfor _, plugins := range [][]Plugin{\n\t\t\t\t{p1, p2},\n\t\t\t\t{p1, p3},\n\t\t\t\t{p1, p4},\n\t\t\t\t{p2, p3},\n\t\t\t\t{p3, p4},\n\n\t\t\t\t{p1, p2, p3},\n\t\t\t\t{p1, p3, p4},\n\t\t\t} {\n\n\t\t\t\tb, err := NewBundleWithOptions(WithName(name),\n\t\t\t\t\tWithVersion(v),\n\t\t\t\t\tWithPlugins(plugins...))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(b.Name()).To(Equal(name))\n\t\t\t\tExpect(b.Version().Compare(v)).To(Equal(0))\n\t\t\t\tversions := b.SupportedProjectVersions()\n\t\t\t\tslices.SortStableFunc(versions, func(a, b config.Version) int {\n\t\t\t\t\treturn a.Compare(b)\n\t\t\t\t})\n\t\t\t\texpectedVersions := CommonSupportedProjectVersions(plugins...)\n\t\t\t\tslices.SortStableFunc(expectedVersions, func(a, b config.Version) int {\n\t\t\t\t\treturn a.Compare(b)\n\t\t\t\t})\n\t\t\t\tExpect(versions).To(Equal(expectedVersions))\n\t\t\t\tExpect(b.Plugins()).To(Equal(plugins))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should accept bundles as input\", func() {\n\t\t\tvar a, b Bundle\n\t\t\tvar err error\n\t\t\tplugins := []Plugin{p1, p2, p3}\n\t\t\ta, err = NewBundleWithOptions(WithName(\"a\"),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithPlugins(p1, p2))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tb, err = NewBundleWithOptions(WithName(\"b\"),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithPlugins(a, p3))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tversions := b.SupportedProjectVersions()\n\t\t\tslices.SortStableFunc(versions, func(a, b config.Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\texpectedVersions := CommonSupportedProjectVersions(plugins...)\n\t\t\tslices.SortStableFunc(expectedVersions, func(a, b config.Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(expectedVersions))\n\t\t\tExpect(b.Plugins()).To(Equal(plugins))\n\t\t})\n\n\t\tIt(\"should fail for plugins with no common supported project version\", func() {\n\t\t\tfor _, plugins := range [][]Plugin{\n\t\t\t\t{p2, p4},\n\n\t\t\t\t{p1, p2, p4},\n\t\t\t\t{p2, p3, p4},\n\n\t\t\t\t{p1, p2, p3, p4},\n\t\t\t} {\n\t\t\t\t_, err := NewBundleWithOptions(WithName(name),\n\t\t\t\t\tWithVersion(v),\n\t\t\t\t\tWithPlugins(plugins...))\n\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t}\n\t\t})\n\t})\n\n\tContext(\"NewBundleWithOptions\", func() {\n\t\tIt(\"should succeed for plugins with common supported project versions\", func() {\n\t\t\tfor _, plugins := range [][]Plugin{\n\t\t\t\t{p1, p2},\n\t\t\t\t{p1, p3},\n\t\t\t\t{p1, p4},\n\t\t\t\t{p2, p3},\n\t\t\t\t{p3, p4},\n\n\t\t\t\t{p1, p2, p3},\n\t\t\t\t{p1, p3, p4},\n\t\t\t} {\n\t\t\t\tb, err := NewBundleWithOptions(WithName(name),\n\t\t\t\t\tWithVersion(v),\n\t\t\t\t\tWithDeprecationMessage(\"\"),\n\t\t\t\t\tWithPlugins(plugins...),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(b.Name()).To(Equal(name))\n\t\t\t\tExpect(b.Version().Compare(v)).To(Equal(0))\n\t\t\t\tversions := b.SupportedProjectVersions()\n\t\t\t\tslices.SortStableFunc(versions, func(a, b config.Version) int {\n\t\t\t\t\treturn a.Compare(b)\n\t\t\t\t})\n\t\t\t\texpectedVersions := CommonSupportedProjectVersions(plugins...)\n\t\t\t\tslices.SortStableFunc(expectedVersions, func(a, b config.Version) int {\n\t\t\t\t\treturn a.Compare(b)\n\t\t\t\t})\n\t\t\t\tExpect(versions).To(Equal(expectedVersions))\n\t\t\t\tExpect(b.Plugins()).To(Equal(plugins))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should accept bundles as input\", func() {\n\t\t\tvar a, b Bundle\n\t\t\tvar err error\n\t\t\tplugins := []Plugin{p1, p2, p3}\n\t\t\ta, err = NewBundleWithOptions(WithName(\"a\"),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithDeprecationMessage(\"\"),\n\t\t\t\tWithPlugins(p1, p2),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tb, err = NewBundleWithOptions(WithName(\"b\"),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithDeprecationMessage(\"\"),\n\t\t\t\tWithPlugins(a, p3),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tversions := b.SupportedProjectVersions()\n\t\t\tslices.SortStableFunc(versions, func(a, b config.Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\texpectedVersions := CommonSupportedProjectVersions(plugins...)\n\t\t\tslices.SortStableFunc(expectedVersions, func(a, b config.Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(expectedVersions))\n\t\t\tExpect(b.Plugins()).To(Equal(plugins))\n\t\t})\n\n\t\tIt(\"should fail for plugins with no common supported project version\", func() {\n\t\t\tfor _, plugins := range [][]Plugin{\n\t\t\t\t{p2, p4},\n\n\t\t\t\t{p1, p2, p4},\n\t\t\t\t{p2, p3, p4},\n\n\t\t\t\t{p1, p2, p3, p4},\n\t\t\t} {\n\t\t\t\t_, err := NewBundleWithOptions(WithName(name),\n\t\t\t\t\tWithVersion(v),\n\t\t\t\t\tWithDeprecationMessage(\"\"),\n\t\t\t\t\tWithPlugins(plugins...),\n\t\t\t\t)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should store and return the deprecation warning\", func() {\n\t\t\tdeprecationMsg := \"This bundle is deprecated, please use v2\"\n\t\t\tb, err := NewBundleWithOptions(\n\t\t\t\tWithName(name),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithDeprecationMessage(deprecationMsg),\n\t\t\t\tWithPlugins(p1),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdeprecated, ok := b.(Deprecated)\n\t\t\tExpect(ok).To(BeTrue())\n\t\t\tExpect(deprecated.DeprecationWarning()).To(Equal(deprecationMsg))\n\t\t})\n\n\t\tIt(\"should return empty string when no deprecation warning is set\", func() {\n\t\t\tb, err := NewBundleWithOptions(\n\t\t\t\tWithName(name),\n\t\t\t\tWithVersion(v),\n\t\t\t\tWithPlugins(p1),\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdeprecated, ok := b.(Deprecated)\n\t\t\tExpect(ok).To(BeTrue())\n\t\t\tExpect(deprecated.DeprecationWarning()).To(BeEmpty())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/errors.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"fmt\"\n)\n\n// ExitError is a typed error that is returned by a plugin when no further steps should be executed for itself.\ntype ExitError struct {\n\tPlugin string\n\tReason string\n}\n\n// Error implements error\nfunc (e ExitError) Error() string {\n\treturn fmt.Sprintf(\"plugin %q exit early: %s\", e.Plugin, e.Reason)\n}\n"
  },
  {
    "path": "pkg/plugin/errors_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"PluginKeyNotFoundError\", func() {\n\tvar err ExitError\n\n\tBeforeEach(func() {\n\t\terr = ExitError{\n\t\t\tPlugin: \"go.kubebuilder.io/v1\",\n\t\t\tReason: \"skipping plugin\",\n\t\t}\n\t})\n\n\tContext(\"Error\", func() {\n\t\tIt(\"should return the correct error message\", func() {\n\t\t\tExpect(err.Error()).To(Equal(\"plugin \\\"go.kubebuilder.io/v1\\\" exit early: skipping plugin\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/external/types.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport \"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\n// PluginRequest contains all information kubebuilder received from the CLI\n// and plugins executed before it.\ntype PluginRequest struct {\n\t// APIVersion defines the versioned schema of PluginRequest that is being sent from Kubebuilder.\n\t// Initially, this will be marked as alpha (v1alpha1).\n\tAPIVersion string `json:\"apiVersion\"`\n\n\t// Args holds the plugin specific arguments that are received from the CLI\n\t// which are to be passed down to the external plugin.\n\tArgs []string `json:\"args\"`\n\n\t// Command contains the command to be executed by the plugin such as init, create api, etc.\n\tCommand string `json:\"command\"`\n\n\t// Universe represents the modified file contents that gets updated over a series of plugin runs\n\t// across the plugin chain. Initially, it starts out as empty.\n\tUniverse map[string]string `json:\"universe\"`\n\n\t// PluginChain contains the full plugin chain being used for this project.\n\t// This allows external plugins to know which other plugins are in use.\n\t// Format: [\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"]\n\tPluginChain []string `json:\"pluginChain,omitempty\"`\n\n\t// Config contains the PROJECT file config. This field may be empty if the\n\t// project is being initialized and the PROJECT file has not been created yet.\n\tConfig map[string]any `json:\"config,omitempty\"`\n}\n\n// PluginResponse is returned to kubebuilder by the plugin and contains all files\n// written by the plugin following a certain command.\ntype PluginResponse struct {\n\t// APIVersion defines the versioned schema of the PluginResponse that is back sent back to Kubebuilder.\n\t// Initially, this will be marked as alpha (v1alpha1)\n\tAPIVersion string `json:\"apiVersion\"`\n\n\t// Command holds the command that gets executed by the plugin such as init, create api, etc.\n\tCommand string `json:\"command\"`\n\n\t// Metadata contains the plugin specific help text that the plugin returns to Kubebuilder when it receives\n\t// `--help` flag from Kubebuilder.\n\tMetadata plugin.SubcommandMetadata `json:\"metadata\"`\n\n\t// Universe in the PluginResponse represents the updated file contents that was written by the plugin.\n\tUniverse map[string]string `json:\"universe\"`\n\n\t// Error is a boolean type that indicates whether there were any errors due to plugin failures.\n\tError bool `json:\"error,omitempty\"`\n\n\t// ErrorMsgs contains the specific error messages of the plugin failures.\n\tErrorMsgs []string `json:\"errorMsgs,omitempty\"`\n\n\t// Flags contains the plugin specific flags that the plugin returns to Kubebuilder when it receives\n\t// a request for a list of supported flags from Kubebuilder\n\tFlags []Flag `json:\"flags,omitempty\"`\n}\n\n// Flag is meant to represent a CLI flag that is used by Kubebuilder to define flags that are parsed\n// for use with an external plugin\ntype Flag struct {\n\t// Name is the name that should be used when creating the flag.\n\t// i.e a name of \"domain\" would become the CLI flag \"--domain\"\n\tName string\n\n\t// Type is the type of flag that should be created. The types that\n\t// Kubebuilder supports are: string, bool, int, and float.\n\t// any value other than the supported will be defaulted to be a string\n\tType string\n\n\t// Default is the default value that should be used for a flag.\n\t// Kubebuilder will attempt to convert this value to the defined\n\t// type for this flag.\n\tDefault string\n\n\t// Usage is a description of the flag and when/why/what it is used for.\n\tUsage string\n}\n"
  },
  {
    "path": "pkg/plugin/filter.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\n// FilterPluginsByKey returns the set of plugins that match the provided key (may be not-fully qualified)\nfunc FilterPluginsByKey(plugins []Plugin, key string) ([]Plugin, error) {\n\tname, ver := SplitKey(key)\n\thasVersion := ver != \"\"\n\tvar version Version\n\tif hasVersion {\n\t\tif err := version.Parse(ver); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tfiltered := make([]Plugin, 0, len(plugins))\n\tfor _, plugin := range plugins {\n\t\tif !strings.HasPrefix(plugin.Name(), name) {\n\t\t\tcontinue\n\t\t}\n\t\tif hasVersion && plugin.Version().Compare(version) != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, plugin)\n\t}\n\treturn filtered, nil\n}\n\n// FilterPluginsByProjectVersion returns the set of plugins that support the provided project version\nfunc FilterPluginsByProjectVersion(plugins []Plugin, projectVersion config.Version) []Plugin {\n\tfiltered := make([]Plugin, 0, len(plugins))\n\tfor _, plugin := range plugins {\n\t\tfor _, supportedVersion := range plugin.SupportedProjectVersions() {\n\t\t\tif supportedVersion.Compare(projectVersion) == 0 {\n\t\t\t\tfiltered = append(filtered, plugin)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn filtered\n}\n"
  },
  {
    "path": "pkg/plugin/filter_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\nvar _ = Describe(\"FilterPlugins\", func() {\n\tvar (\n\t\tp1         mockPlugin\n\t\tp2         mockPlugin\n\t\tp3         mockPlugin\n\t\tp4         mockPlugin\n\t\tp5         mockPlugin\n\t\tallPlugins []Plugin\n\t)\n\n\tBeforeEach(func() {\n\t\tp1 = mockPlugin{\n\t\t\tname:                     \"go.kubebuilder.io\",\n\t\t\tversion:                  Version{Number: 2},\n\t\t\tsupportedProjectVersions: []config.Version{{Number: 2}, {Number: 3}},\n\t\t}\n\t\tp2 = mockPlugin{\n\t\t\tname:                     \"go.kubebuilder.io\",\n\t\t\tversion:                  Version{Number: 3},\n\t\t\tsupportedProjectVersions: []config.Version{{Number: 3}},\n\t\t}\n\t\tp3 = mockPlugin{\n\t\t\tname:                     \"example.kubebuilder.io\",\n\t\t\tversion:                  Version{Number: 1},\n\t\t\tsupportedProjectVersions: []config.Version{{Number: 2}},\n\t\t}\n\t\tp4 = mockPlugin{\n\t\t\tname:                     \"test.kubebuilder.io\",\n\t\t\tversion:                  Version{Number: 1},\n\t\t\tsupportedProjectVersions: []config.Version{{Number: 3}},\n\t\t}\n\t\tp5 = mockPlugin{\n\t\t\tname:                     \"go.test.domain\",\n\t\t\tversion:                  Version{Number: 2},\n\t\t\tsupportedProjectVersions: []config.Version{{Number: 2}},\n\t\t}\n\n\t\tallPlugins = []Plugin{p1, p2, p3, p4, p5}\n\t})\n\n\tDescribeTable(\"should filter by key\",\n\t\tfunc(key string, expectedPlugins func() []Plugin) {\n\t\t\tfiltered, err := FilterPluginsByKey(allPlugins, key)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(filtered).To(Equal(expectedPlugins()))\n\t\t},\n\t\tEntry(\"go plugins\", \"go\", func() []Plugin { return []Plugin{p1, p2, p5} }),\n\t\tEntry(\"go plugins (kubebuilder domain)\", \"go.kubebuilder\", func() []Plugin { return []Plugin{p1, p2} }),\n\t\tEntry(\"go v2 plugins\", \"go/v2\", func() []Plugin { return []Plugin{p1, p5} }),\n\t\tEntry(\"go v2 plugins (kubebuilder domain)\", \"go.kubebuilder/v2\", func() []Plugin { return []Plugin{p1} }),\n\t)\n\n\tIt(\"should fail for invalid versions\", func() {\n\t\t_, err := FilterPluginsByKey(allPlugins, \"go/a\")\n\t\tExpect(err).To(HaveOccurred())\n\t})\n\n\tDescribeTable(\"should filter by project version\",\n\t\tfunc(projectVersion config.Version, expectedPlugins func() []Plugin) {\n\t\t\tExpect(FilterPluginsByProjectVersion(allPlugins, projectVersion)).To(Equal(expectedPlugins()))\n\t\t},\n\t\tEntry(\"project v2 plugins\", config.Version{Number: 2}, func() []Plugin { return []Plugin{p1, p3, p5} }),\n\t\tEntry(\"project v3 plugins\", config.Version{Number: 3}, func() []Plugin { return []Plugin{p1, p2, p4} }),\n\t)\n})\n"
  },
  {
    "path": "pkg/plugin/helpers.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\n// KeyFor returns a Plugin's unique identifying string.\nfunc KeyFor(p Plugin) string {\n\treturn path.Join(p.Name(), p.Version().String())\n}\n\n// SplitKey returns a name and version for a plugin key.\nfunc SplitKey(key string) (string, string) {\n\tif !strings.Contains(key, \"/\") {\n\t\treturn key, \"\"\n\t}\n\tkeyParts := strings.SplitN(key, \"/\", 2)\n\treturn keyParts[0], keyParts[1]\n}\n\n// GetPluginKeyForConfig finds which key to use when saving plugin config.\n// When a plugin is wrapped in a bundle, the bundle's key appears in the chain instead of the plugin's key.\n// For example: \"deploy-image.my-domain/v1-alpha\" wraps \"deploy-image.go.kubebuilder.io/v1-alpha\".\n// Returns the plugin's own key if nothing matches.\nfunc GetPluginKeyForConfig(pluginChain []string, p Plugin) string {\n\tpluginKey := KeyFor(p)\n\n\t// Try exact match first\n\tif slices.Contains(pluginChain, pluginKey) {\n\t\treturn pluginKey\n\t}\n\n\t// No exact match. Try matching by base name + version to find bundled plugins.\n\tpluginName, _ := SplitKey(pluginKey)\n\tpluginVersion := p.Version().String()\n\n\t// Get base name (part before first dot): \"deploy-image.go.kubebuilder.io\" -> \"deploy-image\"\n\tbaseName := pluginName\n\tif before, _, ok := strings.Cut(pluginName, \".\"); ok {\n\t\tbaseName = before\n\t}\n\n\tfor _, key := range pluginChain {\n\t\tname, version := SplitKey(key)\n\t\tif version != pluginVersion {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check if this key matches the base name\n\t\tkeyBaseName := name\n\t\tif before, _, ok := strings.Cut(name, \".\"); ok {\n\t\t\tkeyBaseName = before\n\t\t}\n\n\t\tif keyBaseName == baseName {\n\t\t\treturn key\n\t\t}\n\t}\n\n\t// Nothing matched, use plugin's own key\n\treturn pluginKey\n}\n\n// Validate ensures a Plugin is valid.\nfunc Validate(p Plugin) error {\n\tif err := validateName(p.Name()); err != nil {\n\t\treturn fmt.Errorf(\"invalid plugin name %q: %w\", p.Name(), err)\n\t}\n\tif err := p.Version().Validate(); err != nil {\n\t\treturn fmt.Errorf(\"invalid plugin version %q: %w\", p.Version(), err)\n\t}\n\tif len(p.SupportedProjectVersions()) == 0 {\n\t\treturn fmt.Errorf(\"plugin %q must support at least one project version\", KeyFor(p))\n\t}\n\tfor _, projectVersion := range p.SupportedProjectVersions() {\n\t\tif err := projectVersion.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"plugin %q supports an invalid project version %q: %w\", KeyFor(p), projectVersion, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ValidateKey ensures both plugin name and version are valid.\nfunc ValidateKey(key string) error {\n\tname, version := SplitKey(key)\n\tif err := validateName(name); err != nil {\n\t\treturn fmt.Errorf(\"invalid plugin name %q: %w\", name, err)\n\t}\n\t// CLI-set plugins do not have to contain a version.\n\tif version != \"\" {\n\t\tvar v Version\n\t\tif err := v.Parse(version); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid plugin version %q: %w\", version, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateName ensures name is a valid DNS 1123 subdomain.\nfunc validateName(name string) error {\n\tif errs := validation.IsDNS1123Subdomain(name); len(errs) != 0 {\n\t\treturn fmt.Errorf(\"invalid plugin name %q: %v\", name, errs)\n\t}\n\treturn nil\n}\n\n// SupportsVersion checks if a plugin supports a project version.\nfunc SupportsVersion(p Plugin, projectVersion config.Version) bool {\n\tfor _, version := range p.SupportedProjectVersions() {\n\t\tif projectVersion.Compare(version) == 0 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CommonSupportedProjectVersions returns the projects versions that are supported by all the provided Plugins\nfunc CommonSupportedProjectVersions(plugins ...Plugin) []config.Version {\n\t// Count how many times each supported project version appears\n\tsupportedProjectVersionCounter := make(map[config.Version]int)\n\tfor _, plugin := range plugins {\n\t\tfor _, supportedProjectVersion := range plugin.SupportedProjectVersions() {\n\t\t\tif _, exists := supportedProjectVersionCounter[supportedProjectVersion]; !exists {\n\t\t\t\tsupportedProjectVersionCounter[supportedProjectVersion] = 1\n\t\t\t} else {\n\t\t\t\tsupportedProjectVersionCounter[supportedProjectVersion]++\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check which versions are present the expected number of times\n\tsupportedProjectVersions := make([]config.Version, 0, len(supportedProjectVersionCounter))\n\texpectedTimes := len(plugins)\n\tfor supportedProjectVersion, times := range supportedProjectVersionCounter {\n\t\tif times == expectedTimes {\n\t\t\tsupportedProjectVersions = append(supportedProjectVersions, supportedProjectVersion)\n\t\t}\n\t}\n\n\t// Sort the output to guarantee consistency\n\tslices.SortStableFunc(supportedProjectVersions, func(a, b config.Version) int {\n\t\treturn a.Compare(b)\n\t})\n\n\treturn supportedProjectVersions\n}\n"
  },
  {
    "path": "pkg/plugin/helpers_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"slices\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nconst (\n\tshort = \"go\"\n\tname  = \"go.kubebuilder.io\"\n\tkey   = \"go.kubebuilder.io/v1\"\n)\n\nvar (\n\tversion                  = Version{Number: 1}\n\tsupportedProjectVersions = []config.Version{\n\t\t{Number: 2},\n\t\t{Number: 3},\n\t}\n)\n\nvar _ = Describe(\"KeyFor\", func() {\n\tIt(\"should join plugins name and version\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    name,\n\t\t\tversion: version,\n\t\t}\n\t\tExpect(KeyFor(plugin)).To(Equal(key))\n\t})\n})\n\nvar _ = Describe(\"SplitKey\", func() {\n\tIt(\"should split keys with versions\", func() {\n\t\tn, v := SplitKey(key)\n\t\tExpect(n).To(Equal(name))\n\t\tExpect(v).To(Equal(version.String()))\n\t})\n\n\tIt(\"should split keys without versions\", func() {\n\t\tn, v := SplitKey(name)\n\t\tExpect(n).To(Equal(name))\n\t\tExpect(v).To(Equal(\"\"))\n\t})\n})\n\nvar _ = Describe(\"Validate\", func() {\n\tIt(\"should succeed for valid plugins\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:                     name,\n\t\t\tversion:                  version,\n\t\t\tsupportedProjectVersions: supportedProjectVersions,\n\t\t}\n\t\tExpect(Validate(plugin)).To(Succeed())\n\t})\n\n\tDescribeTable(\"should fail\",\n\t\tfunc(plugin Plugin) {\n\t\t\tExpect(Validate(plugin)).NotTo(Succeed())\n\t\t},\n\t\tEntry(\"for invalid plugin names\", mockPlugin{\n\t\t\tname:                     \"go_kubebuilder.io\",\n\t\t\tversion:                  version,\n\t\t\tsupportedProjectVersions: supportedProjectVersions,\n\t\t}),\n\t\tEntry(\"for invalid plugin versions\", mockPlugin{\n\t\t\tname:                     name,\n\t\t\tversion:                  Version{Number: -1},\n\t\t\tsupportedProjectVersions: supportedProjectVersions,\n\t\t}),\n\t\tEntry(\"for no supported project version\", mockPlugin{\n\t\t\tname:                     name,\n\t\t\tversion:                  version,\n\t\t\tsupportedProjectVersions: nil,\n\t\t}),\n\t\tEntry(\"for invalid supported project version\", mockPlugin{\n\t\t\tname:                     name,\n\t\t\tversion:                  version,\n\t\t\tsupportedProjectVersions: []config.Version{{Number: -1}},\n\t\t}),\n\t)\n})\n\nvar _ = Describe(\"ValidateKey\", func() {\n\tIt(\"should succeed for valid keys\", func() {\n\t\tExpect(ValidateKey(key)).To(Succeed())\n\t})\n\n\tDescribeTable(\"should fail\",\n\t\tfunc(key string) {\n\t\t\tExpect(ValidateKey(key)).NotTo(Succeed())\n\t\t},\n\t\tEntry(\"for invalid plugin names\", \"go_kubebuilder.io/v1\"),\n\t\tEntry(\"for invalid versions\", \"go.kubebuilder.io/a\"),\n\t)\n})\n\nvar _ = Describe(\"SupportsVersion\", func() {\n\tvar plugin mockPlugin\n\n\tBeforeEach(func() {\n\t\tplugin = mockPlugin{\n\t\t\tsupportedProjectVersions: supportedProjectVersions,\n\t\t}\n\t})\n\n\tIt(\"should return true for supported versions\", func() {\n\t\tExpect(SupportsVersion(plugin, config.Version{Number: 2})).To(BeTrue())\n\t\tExpect(SupportsVersion(plugin, config.Version{Number: 3})).To(BeTrue())\n\t})\n\n\tIt(\"should return false for non-supported versions\", func() {\n\t\tExpect(SupportsVersion(plugin, config.Version{Number: 1})).To(BeFalse())\n\t\tExpect(SupportsVersion(plugin, config.Version{Number: 3, Stage: stage.Alpha})).To(BeFalse())\n\t})\n})\n\nvar _ = Describe(\"CommonSupportedProjectVersions\", func() {\n\tIt(\"should return the common version\", func() {\n\t\tvar (\n\t\t\tp1 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2},\n\t\t\t\t{Number: 3},\n\t\t\t}}\n\t\t\tp2 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t\t{Number: 3, Stage: stage.Alpha},\n\t\t\t}}\n\t\t\tp3 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2},\n\t\t\t\t{Number: 3, Stage: stage.Beta},\n\t\t\t}}\n\t\t\tp4 = mockPlugin{supportedProjectVersions: []config.Version{\n\t\t\t\t{Number: 2},\n\t\t\t\t{Number: 3},\n\t\t\t}}\n\t\t)\n\n\t\tfor _, tc := range []struct {\n\t\t\tplugins  []Plugin\n\t\t\tversions []config.Version\n\t\t}{\n\t\t\t{plugins: []Plugin{p1, p2}, versions: []config.Version{{Number: 1}}},\n\t\t\t{plugins: []Plugin{p1, p3}, versions: []config.Version{{Number: 1}, {Number: 2}}},\n\t\t\t{plugins: []Plugin{p1, p4}, versions: []config.Version{{Number: 2}, {Number: 3}}},\n\t\t\t{plugins: []Plugin{p2, p3}, versions: []config.Version{{Number: 1}}},\n\t\t\t{plugins: []Plugin{p2, p4}, versions: []config.Version{}},\n\t\t\t{plugins: []Plugin{p3, p4}, versions: []config.Version{{Number: 2}}},\n\n\t\t\t{plugins: []Plugin{p1, p2, p3}, versions: []config.Version{{Number: 1}}},\n\t\t\t{plugins: []Plugin{p1, p2, p4}, versions: []config.Version{}},\n\t\t\t{plugins: []Plugin{p1, p3, p4}, versions: []config.Version{{Number: 2}}},\n\t\t\t{plugins: []Plugin{p2, p3, p4}, versions: []config.Version{}},\n\n\t\t\t{plugins: []Plugin{p1, p2, p3, p4}, versions: []config.Version{}},\n\t\t} {\n\t\t\tversions := CommonSupportedProjectVersions(tc.plugins...)\n\t\t\tslices.SortStableFunc(versions, func(a, b config.Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(tc.versions))\n\t\t}\n\t})\n})\n\nvar _ = Describe(\"GetPluginKeyForConfig\", func() {\n\tIt(\"should return the plugin's own key when it's in the plugin chain\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t\t\"deploy-image.go.kubebuilder.io/v1-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t})\n\n\tIt(\"should return the bundle key when plugin is wrapped in a bundle\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t\t\"deploy-image.my-domain/v1-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.my-domain/v1-alpha\"))\n\t})\n\n\tIt(\"should fallback to plugin's own key when no match in chain\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t})\n\n\tIt(\"should match on base name and version\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t\t\"deploy-image.operator-sdk.io/v1-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.operator-sdk.io/v1-alpha\"))\n\t})\n\n\tIt(\"should not match if version differs\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t\t\"deploy-image.my-domain/v2-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t})\n\n\tIt(\"should not match if base name differs\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"go.kubebuilder.io/v4\",\n\t\t\t\"other-plugin.my-domain/v1-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.go.kubebuilder.io/v1-alpha\"))\n\t})\n\n\tIt(\"should choose the first matching bundle when multiple candidates exist\", func() {\n\t\tplugin := mockPlugin{\n\t\t\tname:    \"deploy-image.go.kubebuilder.io\",\n\t\t\tversion: Version{Number: 1, Stage: stage.Alpha},\n\t\t}\n\t\tpluginChain := []string{\n\t\t\t\"deploy-image.first.example.com/v1-alpha\",\n\t\t\t\"deploy-image.second.example.com/v1-alpha\",\n\t\t}\n\t\tExpect(GetPluginKeyForConfig(pluginChain, plugin)).To(Equal(\"deploy-image.first.example.com/v1-alpha\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/metadata.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\n// CLIMetadata is the runtime meta-data of the CLI\ntype CLIMetadata struct {\n\t// CommandName is the root command name.\n\tCommandName string\n}\n\n// SubcommandMetadata is the runtime meta-data for a subcommand\ntype SubcommandMetadata struct {\n\t// Description is a description of what this command does. It is used to display help.\n\tDescription string\n\t// Examples are one or more examples of the command-line usage of this command. It is used to display help.\n\tExamples string\n}\n"
  },
  {
    "path": "pkg/plugin/plugin.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\n// Plugin is an interface that defines the common base for all plugins.\ntype Plugin interface {\n\t// Name returns a DNS1123 label string identifying the plugin uniquely. This name should be fully-qualified,\n\t// i.e. have a short prefix describing the plugin type (like a language) followed by a domain.\n\t// For example, Kubebuilder's main plugin would return \"go.kubebuilder.io\".\n\tName() string\n\t// Version returns the plugin's version.\n\t//\n\t// NOTE: this version is different from config version.\n\tVersion() Version\n\t// SupportedProjectVersions lists all project configuration versions this plugin supports.\n\t// The returned slice cannot be empty.\n\tSupportedProjectVersions() []config.Version\n}\n\n// Deprecated is an interface that defines the messages for plugins that are deprecated.\ntype Deprecated interface {\n\t// DeprecationWarning returns a string indicating a plugin is deprecated.\n\tDeprecationWarning() string\n}\n\n// Describable is an optional interface for plugins that provide a short description.\n// This description is shown in help output to explain what the plugin does.\ntype Describable interface {\n\t// Description returns a short description of the plugin (ideally one line).\n\tDescription() string\n}\n\n// Init is an interface for plugins that provide an `init` subcommand.\ntype Init interface {\n\tPlugin\n\t// GetInitSubcommand returns the underlying InitSubcommand interface.\n\tGetInitSubcommand() InitSubcommand\n}\n\n// CreateAPI is an interface for plugins that provide a `create api` subcommand.\ntype CreateAPI interface {\n\tPlugin\n\t// GetCreateAPISubcommand returns the underlying CreateAPISubcommand interface.\n\tGetCreateAPISubcommand() CreateAPISubcommand\n}\n\n// CreateWebhook is an interface for plugins that provide a `create webhook` subcommand.\ntype CreateWebhook interface {\n\tPlugin\n\t// GetCreateWebhookSubcommand returns the underlying CreateWebhookSubcommand interface.\n\tGetCreateWebhookSubcommand() CreateWebhookSubcommand\n}\n\n// Edit is an interface for plugins that provide a `edit` subcommand.\ntype Edit interface {\n\tPlugin\n\t// GetEditSubcommand returns the underlying EditSubcommand interface.\n\tGetEditSubcommand() EditSubcommand\n}\n\n// Full is an interface for plugins that provide `init`, `create api`, `create webhook` and `edit` subcommands.\ntype Full interface {\n\tInit\n\tCreateAPI\n\tCreateWebhook\n\tEdit\n}\n\n// Bundle allows to group plugins under a single key.\ntype Bundle interface {\n\tPlugin\n\t// Plugins returns a list of the bundled plugins.\n\t// The returned list should be flattened, i.e., no plugin bundles should be part of this list.\n\tPlugins() []Plugin\n}\n"
  },
  {
    "path": "pkg/plugin/subcommand.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\n// UpdatesMetadata is an interface that implements the optional metadata update method.\ntype UpdatesMetadata interface {\n\t// UpdateMetadata updates the subcommand metadata.\n\tUpdateMetadata(CLIMetadata, *SubcommandMetadata)\n}\n\n// HasFlags is an interface that implements the optional bind flags method.\ntype HasFlags interface {\n\t// BindFlags binds flags to the CLI subcommand.\n\tBindFlags(*pflag.FlagSet)\n}\n\n// RequiresConfig is an interface that implements the optional inject config method.\ntype RequiresConfig interface {\n\t// InjectConfig injects the configuration to a subcommand.\n\tInjectConfig(config.Config) error\n}\n\n// RequiresResource is an interface that implements the required inject resource method.\ntype RequiresResource interface {\n\t// InjectResource injects the resource model to a subcommand.\n\tInjectResource(*resource.Resource) error\n}\n\n// HasPreScaffold is an interface that implements the optional pre-scaffold method.\ntype HasPreScaffold interface {\n\t// PreScaffold executes tasks before the main scaffolding.\n\tPreScaffold(machinery.Filesystem) error\n}\n\n// Scaffolder is an interface that implements the required scaffold method.\ntype Scaffolder interface {\n\t// Scaffold implements the main scaffolding.\n\tScaffold(machinery.Filesystem) error\n}\n\n// HasPostScaffold is an interface that implements the optional post-scaffold method.\ntype HasPostScaffold interface {\n\t// PostScaffold executes tasks after the main scaffolding.\n\tPostScaffold() error\n}\n\n// Subcommand is a base interface for all subcommands.\ntype Subcommand interface {\n\tScaffolder\n}\n\n// InitSubcommand is an interface that represents an `init` subcommand.\ntype InitSubcommand interface {\n\tSubcommand\n}\n\n// CreateAPISubcommand is an interface that represents a `create api` subcommand.\ntype CreateAPISubcommand interface {\n\tSubcommand\n\tRequiresResource\n}\n\n// CreateWebhookSubcommand is an interface that represents a `create wekbhook` subcommand.\ntype CreateWebhookSubcommand interface {\n\tSubcommand\n\tRequiresResource\n}\n\n// EditSubcommand is an interface that represents an `edit` subcommand.\ntype EditSubcommand interface {\n\tSubcommand\n}\n"
  },
  {
    "path": "pkg/plugin/suite_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\nfunc TestPlugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Plugin Suite\")\n}\n\ntype mockPlugin struct {\n\tname                     string\n\tversion                  Version\n\tsupportedProjectVersions []config.Version\n}\n\nfunc (p mockPlugin) Name() string                               { return p.name }\nfunc (p mockPlugin) Version() Version                           { return p.version }\nfunc (p mockPlugin) SupportedProjectVersions() []config.Version { return p.supportedProjectVersions }\n"
  },
  {
    "path": "pkg/plugin/util/exec.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n)\n\n// RunCmd prints the provided message and command and then executes it binding stdout and stderr\nfunc RunCmd(msg, cmd string, args ...string) error {\n\tc := exec.Command(cmd, args...) //nolint:gosec\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tlog.Info(msg)\n\n\tif err := c.Run(); err != nil {\n\t\treturn fmt.Errorf(\"error running %q: %w\", cmd, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugin/util/exec_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"RunCmd\", func() {\n\tvar (\n\t\toutput *bytes.Buffer\n\t\terr    error\n\t)\n\tBeforeEach(func() {\n\t\toutput = &bytes.Buffer{}\n\t})\n\tAfterEach(func() {\n\t\toutput.Reset()\n\t})\n\tIt(\"executes the command and redirects output to stdout\", func() {\n\t\tcmd := exec.Command(\"echo\", \"test\")\n\t\tcmd.Stdout = output\n\t\terr = cmd.Run()\n\t\tExpect(err).ToNot(HaveOccurred())\n\t\tExpect(strings.TrimSpace(output.String())).To(Equal(\"test\"))\n\t})\n\tIt(\"returns an error if the command fails\", func() {\n\t\terr = RunCmd(\"unknown command\", \"unknowncommand\")\n\t\tExpect(err).To(HaveOccurred())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/util/stdin.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n)\n\n// YesNo reads from stdin looking for one of \"y\", \"yes\", \"n\", \"no\" and returns\n// true for \"y\" and false for \"n\"\nfunc YesNo(reader *bufio.Reader) bool {\n\tfor {\n\t\ttext := readStdin(reader)\n\t\tswitch text {\n\t\tcase \"y\", \"yes\":\n\t\t\treturn true\n\t\tcase \"n\", \"no\":\n\t\t\treturn false\n\t\tdefault:\n\t\t\tfmt.Printf(\"invalid input %q, should be [y/n]\", text)\n\t\t}\n\t}\n}\n\n// readStdin reads a line from stdin trimming spaces, and returns the value.\n// log.Fatal's if there is an error.\nfunc readStdin(reader *bufio.Reader) string {\n\ttext, err := reader.ReadString('\\n')\n\tif err != nil {\n\t\tlog.Fatalf(\"Error when reading input: %v\", err)\n\t}\n\treturn strings.TrimSpace(text)\n}\n"
  },
  {
    "path": "pkg/plugin/util/stdin_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"stdin\", func() {\n\tIt(\"returns true for 'y'\", func() {\n\t\treader := bufio.NewReader(strings.NewReader(\"y\\n\"))\n\t\tExpect(YesNo(reader)).To(BeTrue())\n\t})\n\n\tIt(\"returns true for 'yes'\", func() {\n\t\treader := bufio.NewReader(strings.NewReader(\"yes\\n\"))\n\t\tExpect(YesNo(reader)).To(BeTrue())\n\t})\n\n\tIt(\"returns false for 'n'\", func() {\n\t\treader := bufio.NewReader(strings.NewReader(\"n\\n\"))\n\t\tExpect(YesNo(reader)).To(BeFalse())\n\t})\n\n\tIt(\"returns false for 'no'\", func() {\n\t\treader := bufio.NewReader(strings.NewReader(\"no\\n\"))\n\t\tExpect(YesNo(reader)).To(BeFalse())\n\t})\n\n\tIt(\"prompts again on invalid input\", func() {\n\t\t// \"maybe\" is invalid, then \"y\" is valid\n\t\tinput := \"maybe\\ny\\n\"\n\t\treader := bufio.NewReader(strings.NewReader(input))\n\n\t\t// Capture stdout to check for prompt\n\t\toldStdout := os.Stdout\n\t\t_, w, _ := os.Pipe()\n\t\tos.Stdout = w\n\n\t\t// Call YesNo directly (no goroutine needed)\n\t\tExpect(YesNo(reader)).To(BeTrue())\n\n\t\tExpect(w.Close()).NotTo(HaveOccurred())\n\t\tos.Stdout = oldStdout\n\t})\n\n\tIt(\"trims spaces and works\", func() {\n\t\treader := bufio.NewReader(strings.NewReader(\"  yes  \\n\"))\n\t\tExpect(YesNo(reader)).To(BeTrue())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/util/suite_test.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestStage(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Utils Suite\")\n}\n"
  },
  {
    "path": "pkg/plugin/util/util.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nconst (\n\t// KubebuilderBinName define the name of the kubebuilder binary to be used in the tests\n\tKubebuilderBinName = \"kubebuilder\"\n)\n\n// RandomSuffix returns a 4-letter string.\nfunc RandomSuffix() (string, error) {\n\tsource := []rune(\"abcdefghijklmnopqrstuvwxyz\")\n\tres := make([]rune, 4)\n\tfor i := range res {\n\t\tbi := new(big.Int)\n\t\tr, err := rand.Int(rand.Reader, bi.SetInt64(int64(len(source))))\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to generate random number: %w\", err)\n\t\t}\n\t\tres[i] = source[r.Int64()]\n\t}\n\treturn string(res), nil\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// InsertCode searches target content in the file and insert `toInsert` after the target.\nfunc InsertCode(filename, target, code string) error {\n\t//nolint:gosec // false positive\n\tcontents, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tidx := strings.Index(string(contents), target)\n\tif idx == -1 {\n\t\treturn fmt.Errorf(\"string %s not found in %s\", target, string(contents))\n\t}\n\tout := string(contents[:idx+len(target)]) + code + string(contents[idx+len(target):])\n\t//nolint:gosec // false positive\n\tif errWriteFile := os.WriteFile(filename, []byte(out), 0o644); errWriteFile != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, errWriteFile)\n\t}\n\n\treturn nil\n}\n\n// InsertCodeIfNotExist insert code if it does not already exist\nfunc InsertCodeIfNotExist(filename, target, code string) error {\n\t//nolint:gosec // false positive\n\tcontents, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\n\tidx := strings.Index(string(contents), code)\n\tif idx != -1 {\n\t\treturn nil\n\t}\n\n\treturn InsertCode(filename, target, code)\n}\n\n// AppendCodeIfNotExist checks if the code does not already exist in the file, and if not, appends it to the end.\nfunc AppendCodeIfNotExist(filename, code string) error {\n\tcontents, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\n\tif strings.Contains(string(contents), code) {\n\t\treturn nil // Code already exists, no need to append.\n\t}\n\n\treturn AppendCodeAtTheEnd(filename, code)\n}\n\n// AppendCodeAtTheEnd appends the given code at the end of the file.\nfunc AppendCodeAtTheEnd(filename, code string) error {\n\tf, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open file %q: %w\", filename, err)\n\t}\n\tdefer func() {\n\t\tif err = f.Close(); err != nil {\n\t\t\treturn\n\t\t}\n\t}()\n\n\tif _, errWriteString := f.WriteString(code); errWriteString != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, errWriteString)\n\t}\n\n\treturn nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t//nolint:gosec // false positive\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t}\n\t//nolint:gosec // false positive\n\tif err = os.WriteFile(filename, out.Bytes(), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n\n// CommentCode searches for target in the file and adds the comment prefix\n// to the target content. The target content may span multiple lines.\nfunc CommentCode(filename, target, prefix string) error {\n\t// Read the file content\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\t// Find the target code to be commented\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"failed to find the code %q to be commented\", target)\n\t}\n\n\t// Create a buffer to hold the modified content\n\tout := new(bytes.Buffer)\n\tif _, err = out.Write(content[:idx]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t}\n\n\t// Add the comment prefix to each line of the target code\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tfor scanner.Scan() {\n\t\tif _, err = out.WriteString(prefix + scanner.Text() + \"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t\t}\n\t}\n\n\t// Write the rest of the file content\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q: %w\", filename, err)\n\t}\n\n\t// Write the modified content back to the file\n\tif err = os.WriteFile(filename, out.Bytes(), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n\n// EnsureExistAndReplace check if the content exists and then do the replacement\nfunc EnsureExistAndReplace(input, match, replace string) (string, error) {\n\tif !strings.Contains(input, match) {\n\t\treturn \"\", fmt.Errorf(\"can't find %q\", match)\n\t}\n\treturn strings.ReplaceAll(input, match, replace), nil\n}\n\n// ReplaceInFile replaces all instances of old with new in the file at path.\nfunc ReplaceInFile(path, oldValue, newValue string) error {\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stat file %q: %w\", path, err)\n\t}\n\t//nolint:gosec // false positive\n\tb, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", path, err)\n\t}\n\tif !strings.Contains(string(b), oldValue) {\n\t\treturn errors.New(\"unable to find the content to be replaced\")\n\t}\n\ts := strings.ReplaceAll(string(b), oldValue, newValue)\n\tif err = os.WriteFile(path, []byte(s), info.Mode()); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", path, err)\n\t}\n\treturn nil\n}\n\n// ReplaceRegexInFile finds all strings that match `match` and replaces them\n// with `replace` in the file at path.\n//\n// This function is currently unused in the Kubebuilder codebase,\n// but is used by other projects and may be used in Kubebuilder in the future.\nfunc ReplaceRegexInFile(path, match, replace string) error {\n\tmatcher, err := regexp.Compile(match)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to compile regular expression %q: %w\", match, err)\n\t}\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stat file %q: %w\", path, err)\n\t}\n\t//nolint:gosec // false positive\n\tb, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", path, err)\n\t}\n\ts := matcher.ReplaceAllString(string(b), replace)\n\tif s == string(b) {\n\t\treturn errors.New(\"unable to find the content to be replaced\")\n\t}\n\n\tif err = os.WriteFile(path, []byte(s), info.Mode()); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", path, err)\n\t}\n\n\treturn nil\n}\n\n// HasFileContentWith check if given `text` can be found in file\nfunc HasFileContentWith(path, text string) (bool, error) {\n\t//nolint:gosec\n\tcontents, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to read file %q: %w\", path, err)\n\t}\n\n\treturn strings.Contains(string(contents), text), nil\n}\n"
  },
  {
    "path": "pkg/plugin/util/util_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Cover plugin util helpers\", func() {\n\tDescribe(\"RandomSuffix\", func() {\n\t\tIt(\"should return a string with 4 caracteres\", func() {\n\t\t\tsuffix, err := RandomSuffix()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(suffix).To(HaveLen(4))\n\t\t})\n\n\t\tIt(\"should return different values when call more than once\", func() {\n\t\t\tsuffix1, _ := RandomSuffix()\n\t\t\tsuffix2, _ := RandomSuffix()\n\t\t\tExpect(suffix1).NotTo(Equal(suffix2))\n\t\t})\n\t})\n\n\tDescribe(\"GetNonEmptyLines\", func() {\n\t\tIt(\"should return non-empty lines\", func() {\n\t\t\toutput := \"text1\\n\\ntext2\\ntext3\\n\\n\"\n\t\t\tlines := GetNonEmptyLines(output)\n\t\t\tExpect(lines).To(Equal([]string{\"text1\", \"text2\", \"text3\"}))\n\t\t})\n\n\t\tIt(\"should return an empty when an empty value is passed\", func() {\n\t\t\tlines := GetNonEmptyLines(\"\")\n\t\t\tExpect(lines).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should return same string without empty lines\", func() {\n\t\t\toutput := \"noemptylines\"\n\t\t\tlines := GetNonEmptyLines(output)\n\t\t\tExpect(lines).To(Equal([]string{\"noemptylines\"}))\n\t\t})\n\t})\n\n\tDescribe(\"InsertCode\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tif _, err = os.Stat(path); os.IsNotExist(err) {\n\t\t\t\terr = os.WriteFile(path, []byte(\"exampleTarget\"), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tDescribeTable(\"should not succeed\",\n\t\t\tfunc(target string) {\n\t\t\t\tExpect(InsertCode(path, target, \"exampleCode\")).ShouldNot(Succeed())\n\t\t\t},\n\t\t\tEntry(\"target given is not present in file\", \"randomTarget\"),\n\t\t)\n\n\t\tDescribeTable(\"should succeed\",\n\t\t\tfunc(target string) {\n\t\t\t\tExpect(InsertCode(path, target, \"exampleCode\")).Should(Succeed())\n\t\t\t},\n\t\t\tEntry(\"target given is present in file\", \"exampleTarget\"),\n\t\t)\n\t})\n\n\tDescribe(\"InsertCodeIfNotExist\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.WriteFile(path, []byte(\"target\\n\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should insert code if not present\", func() {\n\t\t\tExpect(InsertCodeIfNotExist(path, \"target\", \"code\\n\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(b)).To(ContainSubstring(\"code\"))\n\t\t})\n\n\t\tIt(\"should not insert code if already present\", func() {\n\t\t\tExpect(InsertCodeIfNotExist(path, \"target\", \"code\\n\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Only one \"code\" should be present\n\t\t\tExpect(strings.Count(string(b), \"code\")).To(Equal(1))\n\t\t})\n\t})\n\n\tDescribe(\"AppendCodeIfNotExist\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.WriteFile(path, []byte(\"foo\\n\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should append code if not present\", func() {\n\t\t\tExpect(AppendCodeIfNotExist(path, \"code\\n\")).To(Succeed())\n\t\t\tb, _ := os.ReadFile(path)\n\t\t\tExpect(string(b)).To(HaveSuffix(\"code\\n\"))\n\t\t})\n\n\t\tIt(\"should not append code if already present\", func() {\n\t\t\tExpect(AppendCodeIfNotExist(path, \"code\\n\")).To(Succeed())\n\t\t\tb, _ := os.ReadFile(path)\n\t\t\tExpect(strings.Count(string(b), \"code\\n\")).To(Equal(1))\n\t\t})\n\t})\n\n\tDescribe(\"UncommentCode and CommentCode\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Write a file with commented lines\n\t\t\terr = os.WriteFile(path, []byte(\"#line1\\n#line2\\nline3\\n\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should uncomment code with prefix\", func() {\n\t\t\ttarget := \"#line1\\n#line2\"\n\t\t\tExpect(UncommentCode(path, target, \"#\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(b)).To(ContainSubstring(\"line1\\nline2\\nline3\\n\"))\n\t\t\tExpect(string(b)).NotTo(ContainSubstring(\"#line1\"))\n\t\t})\n\n\t\tIt(\"should comment code with prefix\", func() {\n\t\t\ttarget := \"line1\\nline2\\n\"\n\t\t\tExpect(CommentCode(path, target, \"#\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(b)).To(ContainSubstring(\"#line1\\n#line2\\n\"))\n\t\t})\n\n\t\tIt(\"should error if target not found for uncomment\", func() {\n\t\t\tExpect(UncommentCode(path, \"notfound\", \"#\")).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should error if target not found for comment\", func() {\n\t\t\tExpect(CommentCode(path, \"notfound\", \"#\")).NotTo(Succeed())\n\t\t})\n\t})\n\n\tDescribe(\"EnsureExistAndReplace\", func() {\n\t\tContext(\"Content Exists\", func() {\n\t\t\tIt(\"should replace all the matched contents\", func() {\n\t\t\t\tgot, err := EnsureExistAndReplace(\"test\", \"t\", \"r\")\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(got).To(Equal(\"resr\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"Content Not Exists\", func() {\n\t\t\tIt(\"should error out\", func() {\n\t\t\t\tgot, err := EnsureExistAndReplace(\"test\", \"m\", \"r\")\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(err.Error()).To(Equal(`can't find \"m\"`))\n\t\t\t\tExpect(got).To(Equal(\"\"))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"ReplaceInFile\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.WriteFile(path, []byte(\"foo bar foo\\nbaz foo\\n\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should replace all occurrences of a string\", func() {\n\t\t\tExpect(ReplaceInFile(path, \"foo\", \"qux\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(b)).To(Equal(\"qux bar qux\\nbaz qux\\n\"))\n\t\t})\n\n\t\tIt(\"should error if oldValue not found\", func() {\n\t\t\tExpect(ReplaceInFile(path, \"notfound\", \"something\")).NotTo(Succeed())\n\t\t})\n\t})\n\n\tDescribe(\"ReplaceRegexInFile\", Ordered, func() {\n\t\tvar (\n\t\t\tcontent []byte\n\t\t\tpath    string\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\tpath = filepath.Join(\"testdata\", \"exampleFile.txt\")\n\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.WriteFile(path, []byte(\"foo123 bar456 foo789\\nbaz000\\n\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontent, err = os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.WriteFile(path, content, 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should error if regex is invalid\", func() {\n\t\t\tExpect(ReplaceRegexInFile(path, `\\K`, \"Z\")).NotTo(Succeed())\n\t\t})\n\n\t\tIt(\"should replace all regex matches\", func() {\n\t\t\tExpect(ReplaceRegexInFile(path, `\\d+`, \"X\")).To(Succeed())\n\t\t\tb, err := os.ReadFile(path)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(b)).To(Equal(\"fooX barX fooX\\nbazX\\n\"))\n\t\t})\n\n\t\tIt(\"should error if regex not found\", func() {\n\t\t\tExpect(ReplaceRegexInFile(path, `notfound`, \"Y\")).NotTo(Succeed())\n\t\t})\n\t})\n\n\tDescribe(\"HasFileContentWith\", Ordered, func() {\n\t\tconst (\n\t\t\tpath    = \"testdata/PROJECT\"\n\t\t\tcontent = `# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ndomain: example.org\nlayout:\n- go.kubebuilder.io/v4\n- helm.kubebuilder.io/v1-alpha\nplugins:\n  helm.kubebuilder.io/v1-alpha: {}\nrepo: github.com/example/repo\nversion: \"3\"\n`\n\t\t)\n\n\t\tBeforeAll(func() {\n\t\t\terr := os.MkdirAll(\"testdata\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tif _, err = os.Stat(path); os.IsNotExist(err) {\n\t\t\t\terr = os.WriteFile(path, []byte(content), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterAll(func() {\n\t\t\terr := os.RemoveAll(\"testdata\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should return true when file contains the expected content\", func() {\n\t\t\tcontent := \"repo: github.com/example/repo\"\n\t\t\tfound, err := HasFileContentWith(path, content)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(found).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return true when file contains multiline expected content\", func() {\n\t\t\tcontent := `plugins:\n  helm.kubebuilder.io/v1-alpha: {}`\n\t\t\tfound, err := HasFileContentWith(path, content)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(found).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return false when file does not contain the expected content\", func() {\n\t\t\tcontent := \"nonExistentContent\"\n\t\t\tfound, err := HasFileContentWith(path, content)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(found).To(BeFalse())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugin/version.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nvar (\n\terrNegative = errors.New(\"plugin version number must be positive\")\n\terrEmpty    = errors.New(\"plugin version is empty\")\n)\n\n// Version is a plugin version containing a positive integer and a stage value that represents stability.\ntype Version struct {\n\t// Number denotes the current version of a plugin. Two different numbers between versions\n\t// indicate that they are incompatible.\n\tNumber int\n\t// Stage indicates stability.\n\tStage stage.Stage\n}\n\n// Parse parses version inline, assuming it adheres to format: (v)?[0-9]*(-(alpha|beta))?\nfunc (v *Version) Parse(version string) error {\n\tversion = strings.TrimPrefix(version, \"v\")\n\tif len(version) == 0 {\n\t\treturn errEmpty\n\t}\n\n\tsubstrings := strings.SplitN(version, \"-\", 2)\n\n\tvar err error\n\tif v.Number, err = strconv.Atoi(substrings[0]); err != nil {\n\t\t// Let's check if the `-` belonged to a negative number\n\t\tif n, errParse := strconv.Atoi(version); errParse == nil && n < 0 {\n\t\t\treturn errNegative\n\t\t}\n\t\treturn fmt.Errorf(\"error converting version number %q: %w\", substrings[0], err)\n\t}\n\n\tif len(substrings) > 1 {\n\t\tif err = v.Stage.Parse(substrings[1]); err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing stage %q: %w\", substrings[1], err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// String returns the string representation of v.\nfunc (v Version) String() string {\n\tstageStr := v.Stage.String()\n\tif len(stageStr) == 0 {\n\t\treturn fmt.Sprintf(\"v%d\", v.Number)\n\t}\n\treturn fmt.Sprintf(\"v%d-%s\", v.Number, stageStr)\n}\n\n// Validate ensures that the version number is positive and the stage is one of the valid stages.\nfunc (v Version) Validate() error {\n\tif v.Number < 0 {\n\t\treturn errNegative\n\t}\n\n\tif err := v.Stage.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"error validating stage %q: %w\", v.Stage, err)\n\t}\n\n\treturn nil\n}\n\n// Compare returns -1 if v < other, 0 if v == other, and 1 if v > other.\nfunc (v Version) Compare(other Version) int {\n\tif v.Number > other.Number {\n\t\treturn 1\n\t} else if v.Number < other.Number {\n\t\treturn -1\n\t}\n\n\treturn v.Stage.Compare(other.Stage)\n}\n\n// IsStable returns true if v is stable.\nfunc (v Version) IsStable() bool {\n\t// Plugin version 0 is not considered stable\n\tif v.Number == 0 {\n\t\treturn false\n\t}\n\n\t// Any other version than 0 depends on its stage field\n\treturn v.Stage.IsStable()\n}\n"
  },
  {
    "path": "pkg/plugin/version_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugin\n\nimport (\n\t\"slices\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n)\n\nvar _ = Describe(\"Version\", func() {\n\tContext(\"Parse\", func() {\n\t\tDescribeTable(\"should be correctly parsed for valid version strings\",\n\t\t\tfunc(str string, number int, s stage.Stage) {\n\t\t\t\tvar v Version\n\t\t\t\tExpect(v.Parse(str)).To(Succeed())\n\t\t\t\tExpect(v.Number).To(Equal(number))\n\t\t\t\tExpect(v.Stage).To(Equal(s))\n\t\t\t},\n\t\t\tEntry(\"for version string `0`\", \"0\", 0, stage.Stable),\n\t\t\tEntry(\"for version string `0-alpha`\", \"0-alpha\", 0, stage.Alpha),\n\t\t\tEntry(\"for version string `0-beta`\", \"0-beta\", 0, stage.Beta),\n\t\t\tEntry(\"for version string `1`\", \"1\", 1, stage.Stable),\n\t\t\tEntry(\"for version string `1-alpha`\", \"1-alpha\", 1, stage.Alpha),\n\t\t\tEntry(\"for version string `1-beta`\", \"1-beta\", 1, stage.Beta),\n\t\t\tEntry(\"for version string `v1`\", \"v1\", 1, stage.Stable),\n\t\t\tEntry(\"for version string `v1-alpha`\", \"v1-alpha\", 1, stage.Alpha),\n\t\t\tEntry(\"for version string `v1-beta`\", \"v1-beta\", 1, stage.Beta),\n\t\t\tEntry(\"for version string `22`\", \"22\", 22, stage.Stable),\n\t\t\tEntry(\"for version string `22-alpha`\", \"22-alpha\", 22, stage.Alpha),\n\t\t\tEntry(\"for version string `22-beta`\", \"22-beta\", 22, stage.Beta),\n\t\t)\n\n\t\tDescribeTable(\"should error when parsing an invalid version string\",\n\t\t\tfunc(str string) {\n\t\t\t\tvar v Version\n\t\t\t\tExpect(v.Parse(str)).NotTo(Succeed())\n\t\t\t},\n\t\t\tEntry(\"for version string ``\", \"\"),\n\t\t\tEntry(\"for version string `-1`\", \"-1\"),\n\t\t\tEntry(\"for version string `-1-alpha`\", \"-1-alpha\"),\n\t\t\tEntry(\"for version string `-1-beta`\", \"-1-beta\"),\n\t\t\tEntry(\"for version string `1.0`\", \"1.0\"),\n\t\t\tEntry(\"for version string `v1.0`\", \"v1.0\"),\n\t\t\tEntry(\"for version string `v1.0-alpha`\", \"v1.0-alpha\"),\n\t\t\tEntry(\"for version string `1.0.0`\", \"1.0.0\"),\n\t\t\tEntry(\"for version string `1-a`\", \"1-a\"),\n\t\t)\n\t})\n\n\tContext(\"String\", func() {\n\t\tDescribeTable(\"should return the correct string value\",\n\t\t\tfunc(version Version, str string) { Expect(version.String()).To(Equal(str)) },\n\t\t\tEntry(\"for version 0\", Version{Number: 0}, \"v0\"),\n\t\t\tEntry(\"for version 0 (stable)\", Version{Number: 0, Stage: stage.Stable}, \"v0\"),\n\t\t\tEntry(\"for version 0 (alpha)\", Version{Number: 0, Stage: stage.Alpha}, \"v0-alpha\"),\n\t\t\tEntry(\"for version 0 (beta)\", Version{Number: 0, Stage: stage.Beta}, \"v0-beta\"),\n\t\t\tEntry(\"for version 0 (implicit)\", Version{}, \"v0\"),\n\t\t\tEntry(\"for version 0 (stable) (implicit)\", Version{Stage: stage.Stable}, \"v0\"),\n\t\t\tEntry(\"for version 0 (alpha) (implicit)\", Version{Stage: stage.Alpha}, \"v0-alpha\"),\n\t\t\tEntry(\"for version 0 (beta) (implicit)\", Version{Stage: stage.Beta}, \"v0-beta\"),\n\t\t\tEntry(\"for version 1\", Version{Number: 1}, \"v1\"),\n\t\t\tEntry(\"for version 1 (stable)\", Version{Number: 1, Stage: stage.Stable}, \"v1\"),\n\t\t\tEntry(\"for version 1 (alpha)\", Version{Number: 1, Stage: stage.Alpha}, \"v1-alpha\"),\n\t\t\tEntry(\"for version 1 (beta)\", Version{Number: 1, Stage: stage.Beta}, \"v1-beta\"),\n\t\t\tEntry(\"for version 22\", Version{Number: 22}, \"v22\"),\n\t\t\tEntry(\"for version 22 (stable)\", Version{Number: 22, Stage: stage.Stable}, \"v22\"),\n\t\t\tEntry(\"for version 22 (alpha)\", Version{Number: 22, Stage: stage.Alpha}, \"v22-alpha\"),\n\t\t\tEntry(\"for version 22 (beta)\", Version{Number: 22, Stage: stage.Beta}, \"v22-beta\"),\n\t\t)\n\t})\n\n\tContext(\"Validate\", func() {\n\t\tDescribeTable(\"should validate valid versions\",\n\t\t\tfunc(version Version) { Expect(version.Validate()).To(Succeed()) },\n\t\t\tEntry(\"for version 0\", Version{Number: 0}),\n\t\t\tEntry(\"for version 0 (stable)\", Version{Number: 0, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha)\", Version{Number: 0, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta)\", Version{Number: 0, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 0 (implicit)\", Version{}),\n\t\t\tEntry(\"for version 0 (stable) (implicit)\", Version{Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha) (implicit)\", Version{Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta) (implicit)\", Version{Stage: stage.Beta}),\n\t\t\tEntry(\"for version 1\", Version{Number: 1}),\n\t\t\tEntry(\"for version 1 (stable)\", Version{Number: 1, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 1 (alpha)\", Version{Number: 1, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 1 (beta)\", Version{Number: 1, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 22\", Version{Number: 22}),\n\t\t\tEntry(\"for version 22 (stable)\", Version{Number: 22, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 22 (alpha)\", Version{Number: 22, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 22 (beta)\", Version{Number: 22, Stage: stage.Beta}),\n\t\t)\n\n\t\tDescribeTable(\"should fail for invalid versions\",\n\t\t\tfunc(version Version) { Expect(version.Validate()).NotTo(Succeed()) },\n\t\t\tEntry(\"for version -1\", Version{Number: -1}),\n\t\t\tEntry(\"for version -1 (stable)\", Version{Number: -1, Stage: stage.Stable}),\n\t\t\tEntry(\"for version -1 (alpha)\", Version{Number: -1, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version -1 (beta)\", Version{Number: -1, Stage: stage.Beta}),\n\t\t\tEntry(\"for invalid stage\", Version{Stage: stage.Stage(34)}),\n\t\t)\n\t})\n\n\tContext(\"Compare\", func() {\n\t\t// Test Compare() by sorting a list.\n\t\tvar (\n\t\t\tversions       []Version\n\t\t\tsortedVersions []Version\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tversions = []Version{\n\t\t\t\t{Number: 2, Stage: stage.Alpha},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t\t{Number: 4, Stage: stage.Beta},\n\t\t\t\t{Number: 1, Stage: stage.Alpha},\n\t\t\t\t{Number: 4},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 30},\n\t\t\t\t{Number: 4, Stage: stage.Alpha},\n\t\t\t}\n\n\t\t\tsortedVersions = []Version{\n\t\t\t\t{Number: 1, Stage: stage.Alpha},\n\t\t\t\t{Number: 1},\n\t\t\t\t{Number: 2, Stage: stage.Alpha},\n\t\t\t\t{Number: 2, Stage: stage.Beta},\n\t\t\t\t{Number: 4, Stage: stage.Alpha},\n\t\t\t\t{Number: 4, Stage: stage.Beta},\n\t\t\t\t{Number: 4},\n\t\t\t\t{Number: 30},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t\t{Number: 44, Stage: stage.Alpha},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"sorts a valid list of versions correctly\", func() {\n\t\t\tslices.SortStableFunc(versions, func(a, b Version) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(sortedVersions))\n\t\t})\n\t})\n\n\tContext(\"IsStable\", func() {\n\t\tDescribeTable(\"should return true for stable versions\",\n\t\t\tfunc(version Version) { Expect(version.IsStable()).To(BeTrue()) },\n\t\t\tEntry(\"for version 1\", Version{Number: 1}),\n\t\t\tEntry(\"for version 1 (stable)\", Version{Number: 1, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 22\", Version{Number: 22}),\n\t\t\tEntry(\"for version 22 (stable)\", Version{Number: 22, Stage: stage.Stable}),\n\t\t)\n\n\t\tDescribeTable(\"should return false for unstable versions\",\n\t\t\tfunc(version Version) { Expect(version.IsStable()).To(BeFalse()) },\n\t\t\tEntry(\"for version 0\", Version{Number: 0}),\n\t\t\tEntry(\"for version 0 (stable)\", Version{Number: 0, Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha)\", Version{Number: 0, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta)\", Version{Number: 0, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 0 (implicit)\", Version{}),\n\t\t\tEntry(\"for version 0 (stable) (implicit)\", Version{Stage: stage.Stable}),\n\t\t\tEntry(\"for version 0 (alpha) (implicit)\", Version{Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 0 (beta) (implicit)\", Version{Stage: stage.Beta}),\n\t\t\tEntry(\"for version 1 (alpha)\", Version{Number: 1, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 1 (beta)\", Version{Number: 1, Stage: stage.Beta}),\n\t\t\tEntry(\"for version 22 (alpha)\", Version{Number: 22, Stage: stage.Alpha}),\n\t\t\tEntry(\"for version 22 (beta)\", Version{Number: 22, Stage: stage.Beta}),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds\"\n)\n\nvar _ plugin.CreateAPISubcommand = &createAPISubcommand{}\n\ntype createAPISubcommand struct {\n\tcreateSubcommand\n}\n\nfunc (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold api subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/create.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\ntype createSubcommand struct {\n\tconfig   config.Config\n\tresource *resource.Resource\n\n\t// force indicates whether to scaffold files even if they exist.\n\tforce bool\n}\n\nfunc (p *createSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.force, \"force\", false, \"overwrite existing files\")\n}\n\nfunc (p *createSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *createSubcommand) InjectResource(res *resource.Resource) error {\n\tp.resource = res\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/create_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"createSubcommand\", func() {\n\tvar (\n\t\tsubCmd *createSubcommand\n\t\tcfg    config.Config\n\t\tres    *resource.Resource\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &createSubcommand{}\n\t\tcfg = cfgv3.New()\n\t\tres = &resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"crew\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Captain\",\n\t\t\t},\n\t\t}\n\t})\n\n\tIt(\"should bind force flag and receive value via merge/sync\", func() {\n\t\tflags := pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\t\tsubCmd.BindFlags(flags)\n\n\t\terr := flags.Set(\"force\", \"true\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(subCmd.force).To(BeTrue())\n\t})\n\n\tIt(\"should inject config and resource successfully\", func() {\n\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t\tExpect(subCmd.config).To(Equal(cfg))\n\n\t\tExpect(subCmd.InjectResource(res)).To(Succeed())\n\t\tExpect(subCmd.resource).To(Equal(res))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds\"\n)\n\nvar _ plugin.InitSubcommand = &initSubcommand{}\n\ntype initSubcommand struct {\n\tconfig config.Config\n\n\t// config options\n\tdomain string\n\tname   string\n}\n\nfunc (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = `Initialize a common project including the following files:\n  - a \"PROJECT\" file that stores project configuration\n  - several YAML files for project deployment under the \"config\" directory\n\nNOTE: This plugin requires kustomize version v5 and kubectl >= 1.22.\n`\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Initialize a common project with your domain and name in copyright\n  %[1]s init --plugins %[2]s --domain example.org\n\n  # Initialize a common project defining a specific project version\n  %[1]s init --plugins %[2]s --project-version 3\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.StringVar(&p.domain, \"domain\", \"my.domain\", \"domain for groups\")\n\tfs.StringVar(&p.name, \"project-name\", \"\", \"name of this project\")\n}\n\nfunc (p *initSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\tif err := p.config.SetDomain(p.domain); err != nil {\n\t\treturn fmt.Errorf(\"error setting domain: %w\", err)\n\t}\n\n\t// Assign a default project name\n\tif p.name == \"\" {\n\t\tdir, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting current directory: %w\", err)\n\t\t}\n\t\tp.name = strings.ToLower(filepath.Base(dir))\n\t}\n\t// Check if the project name is a valid k8s namespace (DNS 1123 label).\n\tif err := validation.IsDNS1123Label(p.name); err != nil {\n\t\treturn fmt.Errorf(\"project name %q is invalid: %v\", p.name, err)\n\t}\n\n\tif err := p.config.SetProjectName(p.name); err != nil {\n\t\treturn fmt.Errorf(\"error setting project name: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewInitScaffolder(p.config)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold init subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/init_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nconst testDomain = \"example.com\"\n\nvar _ = Describe(\"initSubcommand\", func() {\n\tvar (\n\t\tsubCmd *initSubcommand\n\t\tcfg    config.Config\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &initSubcommand{}\n\t\tcfg = cfgv3.New()\n\t})\n\n\tIt(\"should set domain and project name\", func() {\n\t\tsubCmd.domain = testDomain\n\t\tsubCmd.name = \"my-project\"\n\t\terr := subCmd.InjectConfig(cfg)\n\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(cfg.GetDomain()).To(Equal(testDomain))\n\t\tExpect(cfg.GetProjectName()).To(Equal(\"my-project\"))\n\t})\n\n\tIt(\"should derive project name from directory when not provided\", func() {\n\t\toriginalDir, err := os.Getwd()\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tdefer func() { _ = os.Chdir(originalDir) }()\n\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"test-project-name\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tdefer func() { _ = os.RemoveAll(tmpDir) }()\n\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tsubCmd.domain = testDomain\n\t\tsubCmd.name = \"\"\n\t\terr = subCmd.InjectConfig(cfg)\n\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(cfg.GetProjectName()).To(Equal(filepath.Base(tmpDir)))\n\t})\n\n\tIt(\"should reject invalid DNS 1123 label project names\", func() {\n\t\tsubCmd.domain = testDomain\n\t\tsubCmd.name = \"Invalid_Project\"\n\n\t\terr := subCmd.InjectConfig(cfg)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"is invalid\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/plugin.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n)\n\n// KustomizeVersion is the kubernetes-sigs/kustomize version to be used in the project\nconst KustomizeVersion = \"v5.8.1\"\n\nconst pluginName = \"kustomize.common.\" + plugins.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 2, Stage: stage.Stable}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\nvar (\n\t_ plugin.Init          = Plugin{}\n\t_ plugin.CreateAPI     = Plugin{}\n\t_ plugin.CreateWebhook = Plugin{}\n)\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\tinitSubcommand\n\tcreateAPISubcommand\n\tcreateWebhookSubcommand\n}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetInitSubcommand will return the subcommand which is responsible for scaffolding init project\nfunc (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }\n\n// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis\nfunc (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }\n\n// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks\nfunc (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {\n\treturn &p.createWebhookSubcommand\n}\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Scaffolds base Kustomize configuration\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/plugin_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nvar _ = Describe(\"Plugin\", func() {\n\tvar p Plugin\n\n\tIt(\"should have correct version and support v3 projects\", func() {\n\t\tExpect(p.Version().Number).To(Equal(2))\n\t\tExpect(p.SupportedProjectVersions()).To(ContainElement(cfgv3.Version))\n\t})\n\n\tIt(\"should not be deprecated\", func() {\n\t\tExpect(p.DeprecationWarning()).To(BeEmpty())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/samples\"\n)\n\nvar _ plugins.Scaffolder = &apiScaffolder{}\n\n// apiScaffolder contains configuration for generating scaffolding for Go type\n// representing the API and controller that implements the behavior for the API.\ntype apiScaffolder struct {\n\tconfig   config.Config\n\tresource resource.Resource\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n\n\t// force indicates whether to scaffold files even if they exist.\n\tforce bool\n}\n\n// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations\nfunc NewAPIScaffolder(cfg config.Config, res resource.Resource, force bool) plugins.Scaffolder {\n\treturn &apiScaffolder{\n\t\tconfig:   cfg,\n\t\tresource: res,\n\t\tforce:    force,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) Scaffold() error {\n\tlog.Info(\"Writing kustomize manifests for you to edit...\")\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t\tmachinery.WithResource(&s.resource),\n\t)\n\n\t// Keep track of these values before the update\n\tif s.resource.HasAPI() {\n\t\tif err := scaffold.Execute(\n\t\t\t&samples.CRDSample{Force: s.force},\n\t\t\t&rbac.CRDAdminRole{},\n\t\t\t&rbac.CRDEditorRole{},\n\t\t\t&rbac.CRDViewerRole{},\n\t\t\t&crd.Kustomization{},\n\t\t\t&crd.KustomizeConfig{},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding kustomize API manifests: %w\", err)\n\t\t}\n\n\t\t// If the gvk is non-empty\n\t\tif s.resource.Group != \"\" || s.resource.Version != \"\" || s.resource.Kind != \"\" {\n\t\t\tif err := scaffold.Execute(&samples.Kustomization{}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error scaffolding manifests: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\terr := pluginutil.UncommentCode(kustomizeFilePath, \"#- ../crd\", `#`)\n\t\tif err != nil {\n\t\t\thasCRUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, \"- ../crd\")\n\t\t\tif !hasCRUncommented || errCheck != nil {\n\t\t\t\tlog.Error(\"unable to find the target #- ../crd to uncomment in the file\",\n\t\t\t\t\t\"file_path\", kustomizeFilePath)\n\t\t\t}\n\t\t}\n\n\t\tcomment := fmt.Sprintf(adminEditViewRulesCommentFragment, s.config.GetProjectName())\n\n\t\t// Add scaffolded CRD Admin, Editor and Viewer roles in config/rbac/kustomization.yaml\n\t\trbacKustomizeFilePath := \"config/rbac/kustomization.yaml\"\n\t\terr = pluginutil.AppendCodeIfNotExist(rbacKustomizeFilePath,\n\t\t\tcomment)\n\t\tif err != nil {\n\t\t\tlog.Error(\"failed to append the admin/edit/view roles comment in the file\",\n\t\t\t\t\"file_path\", rbacKustomizeFilePath)\n\t\t}\n\t\tcrdName := strings.ToLower(s.resource.Kind)\n\t\tif s.config.IsMultiGroup() && s.resource.Group != \"\" {\n\t\t\tcrdName = strings.ToLower(s.resource.Group) + \"_\" + crdName\n\t\t}\n\t\terr = pluginutil.InsertCodeIfNotExist(rbacKustomizeFilePath, comment,\n\t\t\tfmt.Sprintf(\"\\n- %[1]s_admin_role.yaml\\n- %[1]s_editor_role.yaml\\n- %[1]s_viewer_role.yaml\", crdName))\n\t\tif err != nil {\n\t\t\tlog.Error(\"failed to add admin, editor and viewer roles in the file\",\n\t\t\t\t\"file_path\", rbacKustomizeFilePath)\n\t\t}\n\t\t// Add an empty line at the end of the file\n\t\terr = pluginutil.AppendCodeIfNotExist(rbacKustomizeFilePath,\n\t\t\t`\n\n`)\n\t\tif err != nil {\n\t\t\tlog.Error(\"failed to append empty line at the end of the file\",\n\t\t\t\t\"file_path\", rbacKustomizeFilePath)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nconst adminEditViewRulesCommentFragment = `# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the %s itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/edit.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac\"\n)\n\nvar _ plugins.Scaffolder = &editScaffolder{}\n\ntype editScaffolder struct {\n\tconfig     config.Config\n\tnamespaced bool\n\tforce      bool\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewEditScaffolder returns a new Scaffolder for configuration edit operations\nfunc NewEditScaffolder(cfg config.Config, namespaced bool, force bool) plugins.Scaffolder {\n\treturn &editScaffolder{\n\t\tconfig:     cfg,\n\t\tnamespaced: namespaced,\n\t\tforce:      force,\n\t}\n}\n\n// InjectFS implements plugins.Scaffolder\nfunc (s *editScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements plugins.Scaffolder\nfunc (s *editScaffolder) Scaffold() error {\n\t// Initialize the machinery.Scaffold\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\tvar templates []machinery.Builder\n\n\tif s.namespaced {\n\t\t// Scaffold namespace-scoped RBAC and manager config\n\t\ttemplates = []machinery.Builder{\n\t\t\t&rbac.NamespacedRole{},\n\t\t\t&rbac.NamespacedRoleBinding{},\n\t\t\t&manager.Config{Image: imageName, Force: s.force},\n\t\t}\n\t} else {\n\t\t// Scaffold cluster-scoped RBAC and manager config\n\t\ttemplates = []machinery.Builder{\n\t\t\t&rbac.ClusterRole{},\n\t\t\t&rbac.ClusterRoleBinding{},\n\t\t\t&manager.Config{Image: imageName, Force: s.force},\n\t\t}\n\t}\n\n\tif err := scaffold.Execute(templates...); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold: %w\", err)\n\t}\n\n\t// Regenerate CRD admin/editor/viewer roles for all existing resources\n\t// to match the new namespaced/cluster-scoped configuration\n\tresources, err := s.config.GetResources()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get resources: %w\", err)\n\t}\n\n\tfor _, res := range resources {\n\t\tif res.HasAPI() {\n\t\t\t// Create a scaffold with the resource injected for each resource\n\t\t\tresourceScaffold := machinery.NewScaffold(s.fs,\n\t\t\t\tmachinery.WithConfig(s.config),\n\t\t\t\tmachinery.WithResource(&res),\n\t\t\t)\n\n\t\t\tif err := resourceScaffold.Execute(\n\t\t\t\t&rbac.CRDAdminRole{},\n\t\t\t\t&rbac.CRDEditorRole{},\n\t\t\t\t&rbac.CRDViewerRole{},\n\t\t\t); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to scaffold CRD roles for %s: %w\", res.Kind, err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager\"\n\tnetworkpolicy \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/prometheus\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac\"\n)\n\nconst (\n\timageName = \"controller:latest\"\n)\n\nvar _ plugins.Scaffolder = &initScaffolder{}\n\ntype initScaffolder struct {\n\tconfig config.Config\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewInitScaffolder returns a new Scaffolder for project initialization operations\nfunc NewInitScaffolder(cfg config.Config) plugins.Scaffolder {\n\treturn &initScaffolder{\n\t\tconfig: cfg,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *initScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *initScaffolder) Scaffold() error {\n\tlog.Info(\"Writing kustomize manifests for you to edit...\")\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\ttemplates := []machinery.Builder{\n\t\t&rbac.Kustomization{},\n\t\t&kdefault.MetricsService{},\n\t\t&rbac.MetricsAuthRole{},\n\t\t&rbac.MetricsAuthRoleBinding{},\n\t\t&rbac.MetricsReaderRole{},\n\t\t&rbac.LeaderElectionRole{},\n\t\t&rbac.LeaderElectionRoleBinding{},\n\t\t&rbac.ServiceAccount{},\n\t\t&manager.Kustomization{},\n\t\t&kdefault.ManagerMetricsPatch{},\n\t\t&kdefault.CertManagerMetricsPatch{},\n\t\t&manager.Config{Image: imageName},\n\t\t&kdefault.Kustomization{},\n\t\t&networkpolicy.Kustomization{},\n\t\t&networkpolicy.PolicyAllowMetrics{},\n\t\t&prometheus.Kustomization{},\n\t\t&prometheus.Monitor{},\n\t\t&prometheus.ServiceMonitorPatch{},\n\t}\n\n\t// Scaffold appropriate RBAC based on scope\n\t// We need to create a Role/ClusterRole because if the project\n\t// has no CRDs defined, controller-gen will not generate this file\n\tif s.config.IsNamespaced() {\n\t\ttemplates = append(templates,\n\t\t\t&rbac.NamespacedRoleBinding{},\n\t\t\t&rbac.NamespacedRole{},\n\t\t)\n\t} else {\n\t\ttemplates = append(templates,\n\t\t\t&rbac.ClusterRoleBinding{},\n\t\t\t&rbac.ClusterRole{},\n\t\t)\n\t}\n\n\tif err := scaffold.Execute(templates...); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold kustomize manifests: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager/certificate_metrics.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &MetricsCertificate{}\n\n// MetricsCertificate scaffolds a file that defines the issuer CR and the metrics certificate CR\ntype MetricsCertificate struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *MetricsCertificate) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"certmanager\", \"certificate-metrics.yaml\")\n\t}\n\n\tf.TemplateBody = metricsCertManagerTemplate\n\n\t// If file exists, skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\n//nolint:lll\nconst metricsCertManagerTemplate = `# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager/certificate_webhook.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Certificate{}\n\n// Certificate scaffolds a file that defines the issuer CR and the certificate CR\ntype Certificate struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Certificate) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"certmanager\", \"certificate-webhook.yaml\")\n\t}\n\n\tf.TemplateBody = certManagerTemplate\n\n\t// If file exists (ex. because a webhook was already created), skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst certManagerTemplate = `# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager/issuer.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Issuer{}\n\n// Issuer scaffolds a file that defines the self-signed Issuer CR\ntype Issuer struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Issuer) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"certmanager\", \"issuer.yaml\")\n\t}\n\n\tf.TemplateBody = issuerTemplate\n\n\t// If file exists, skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst issuerTemplate = `# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the certmanager folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"certmanager\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizationTemplate\n\n\t// If file exists (ex. because a webhook was already created), skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst kustomizationTemplate = `resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &KustomizeConfig{}\n\n// KustomizeConfig scaffolds a file that configures the kustomization for the certmanager folder\ntype KustomizeConfig struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *KustomizeConfig) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"certmanager\", \"kustomizeconfig.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeConfigTemplate\n\n\t// If file exists (ex. because a webhook was already created), skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst kustomizeConfigTemplate = `# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage crd\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar (\n\t_ machinery.Template = &Kustomization{}\n\t_ machinery.Inserter = &Kustomization{}\n)\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the crd folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"crd\", \"kustomization.yaml\")\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tf.TemplateBody = fmt.Sprintf(kustomizationTemplate,\n\t\tmachinery.NewMarkerFor(f.Path, resourceMarker),\n\t\tmachinery.NewMarkerFor(f.Path, webhookPatchMarker),\n\t)\n\n\treturn nil\n}\n\n//nolint:gosec // to ignore false complain G101: Potential hardcoded credentials (gosec)\nconst (\n\tresourceMarker     = \"crdkustomizeresource\"\n\twebhookPatchMarker = \"crdkustomizewebhookpatch\"\n)\n\n// GetMarkers implements file.Inserter\nfunc (f *Kustomization) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(f.Path, resourceMarker),\n\t\tmachinery.NewMarkerFor(f.Path, webhookPatchMarker),\n\t}\n}\n\nconst (\n\tresourceCodeFragment = `- bases/%s_%s.yaml\n`\n\twebhookPatchCodeFragment = `- path: patches/webhook_in_%s.yaml\n`\n)\n\n// GetCodeFragments implements file.Inserter\nfunc (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap {\n\tfragments := make(machinery.CodeFragmentsMap, 2)\n\n\t// Generate resource code fragments\n\tres := make([]string, 0, 1)\n\tres = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural))\n\n\tsuffix := f.Resource.Plural\n\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\tsuffix = f.Resource.Group + \"_\" + f.Resource.Plural\n\t}\n\n\tif !f.Resource.Webhooks.IsEmpty() && f.Resource.Webhooks.Conversion {\n\t\twebhookPatch := fmt.Sprintf(webhookPatchCodeFragment, suffix)\n\n\t\tmarker := machinery.NewMarkerFor(f.Path, webhookPatchMarker)\n\t\tif _, exists := fragments[marker]; !exists {\n\t\t\tfragments[marker] = []string{webhookPatch}\n\t\t}\n\t}\n\n\t// Only store code fragments in the map if the slices are non-empty\n\tif len(res) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, resourceMarker)] = res\n\t}\n\n\treturn fragments\n}\n\nvar kustomizationTemplate = `# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n%s\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n%s\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\n#configurations:\n#- kustomizeconfig.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage crd\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &KustomizeConfig{}\n\n// KustomizeConfig  scaffolds a file that configures the kustomization for the crd folder\ntype KustomizeConfig struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *KustomizeConfig) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"crd\", \"kustomizeconfig.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeConfigTemplate\n\n\treturn nil\n}\n\n//nolint:lll\nconst kustomizeConfigTemplate = `# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patches\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &EnableCAInjectionPatch{}\n\n// EnableCAInjectionPatch scaffolds a file that defines the patch that injects CA into the CRD\ntype EnableCAInjectionPatch struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *EnableCAInjectionPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"crd\", \"patches\", \"cainjection_in_%[group]_%[plural].yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"crd\", \"patches\", \"cainjection_in_%[plural].yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tf.TemplateBody = enableCAInjectionPatchTemplate\n\n\treturn nil\n}\n\nconst enableCAInjectionPatchTemplate = `# The following patch adds a directive for certmanager to inject CA into the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME\n  name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }}\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage patches\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &EnableWebhookPatch{}\n\n// EnableWebhookPatch scaffolds a file that defines the patch that enables conversion webhook for the CRD\ntype EnableWebhookPatch struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *EnableWebhookPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"crd\", \"patches\", \"webhook_in_%[group]_%[plural].yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"crd\", \"patches\", \"webhook_in_%[plural].yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tf.TemplateBody = enableWebhookPatchTemplate\n\n\treturn nil\n}\n\nconst enableWebhookPatchTemplate = `# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }}\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/cert_metrics_manager_patch.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CertManagerMetricsPatch{}\n\n// CertManagerMetricsPatch scaffolds a file that defines the patch that enables webhooks on the manager\ntype CertManagerMetricsPatch struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CertManagerMetricsPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"cert_metrics_manager_patch.yaml\")\n\t}\n\n\tf.TemplateBody = metricsManagerPatchTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\t// If file exists (ex. because a webhook was already created), skip creation.\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst metricsManagerPatchTemplate = `# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the default overlay folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeTemplate\n\n\tf.IfExistsAction = machinery.Error\n\n\treturn nil\n}\n\nconst kustomizeTemplate = `# Adds namespace to all resources.\nnamespace: {{ .ProjectName }}-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: {{ .ProjectName }}-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n#- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n#- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- path: manager_webhook_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\n#replacements:\n# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have any webhook\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.name # Name of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.namespace # Namespace of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert # This name should match the one in certificate.yaml\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n\n# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/kustomization_conversion_updater.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst (\n\tcaNamespace = \"crdkustomizecainjectionns\"\n\tcaName      = \"crdkustomizecainjectionname\"\n)\n\n// KustomizationCAConversionUpdater appends CA injection targets for CRDs with --conversion\ntype KustomizationCAConversionUpdater struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults defines the file path and behavior for existing files\nfunc (f *KustomizationCAConversionUpdater) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"kustomization.yaml\")\n\t}\n\tf.IfExistsAction = machinery.SkipFile // Only append to the existing file, don’t overwrite it\n\treturn nil\n}\n\n// GetMarkers provides the markers where the CA injection targets will be appended\nfunc (f *KustomizationCAConversionUpdater) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(f.Path, caNamespace),\n\t\tmachinery.NewMarkerFor(f.Path, caName),\n\t}\n}\n\n// GetCodeFragments appends CA injection targets for the CRD with --conversion as comments\nfunc (f *KustomizationCAConversionUpdater) GetCodeFragments() machinery.CodeFragmentsMap {\n\tfragments := make(machinery.CodeFragmentsMap)\n\n\t// Obtain the formatted CRD name as Plural.Group.Domain (e.g., cronjobs.batch.tutorial.kubebuilder.io)\n\tcrdName := fmt.Sprintf(\"%s.%s\", f.Resource.Plural, f.Resource.QualifiedGroup())\n\n\tif !f.Resource.Webhooks.IsEmpty() && f.Resource.Webhooks.Conversion {\n\t\t// Commented CA injection configuration for the namespace part\n\t\tcaInjectionNamespace := fmt.Sprintf(`#     - select:\n#         kind: CustomResourceDefinition\n#         name: %s\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n`, crdName)\n\n\t\t// Commented CA injection configuration for the name part\n\t\tcaInjectionName := fmt.Sprintf(`#     - select:\n#         kind: CustomResourceDefinition\n#         name: %s\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n`, crdName)\n\n\t\t// Append to the correct markers to prevent duplication\n\t\tnamespaceMarker := machinery.NewMarkerFor(f.Path, caNamespace)\n\t\tcertificateMarker := machinery.NewMarkerFor(f.Path, caName)\n\n\t\t// Check if the fragments already exist before adding them\n\t\tif _, exists := fragments[namespaceMarker]; !exists {\n\t\t\tfragments[namespaceMarker] = []string{caInjectionNamespace}\n\t\t}\n\t\tif _, exists := fragments[certificateMarker]; !exists {\n\t\t\tfragments[certificateMarker] = []string{caInjectionName}\n\t\t}\n\t}\n\n\treturn fragments\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/manager_metrics_patch.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ManagerMetricsPatch{}\n\n// ManagerMetricsPatch scaffolds a file that defines the patch that enables prometheus metrics for the manager\ntype ManagerMetricsPatch struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ManagerMetricsPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"manager_metrics_patch.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeMetricsPatchTemplate\n\n\tf.IfExistsAction = machinery.Error\n\n\treturn nil\n}\n\nconst kustomizeMetricsPatchTemplate = `# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/metrics_service.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &MetricsService{}\n\n// MetricsService scaffolds a file that defines the service for the auth proxy\ntype MetricsService struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *MetricsService) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"metrics_service.yaml\")\n\t}\n\n\tf.TemplateBody = metricsServiceTemplate\n\n\treturn nil\n}\n\nconst metricsServiceTemplate = `apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kdefault\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ManagerWebhookPatch{}\n\n// ManagerWebhookPatch scaffolds a file that defines the patch that enables webhooks on the manager\ntype ManagerWebhookPatch struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ManagerWebhookPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"default\", \"manager_webhook_patch.yaml\")\n\t}\n\n\tf.TemplateBody = managerWebhookPatchTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\t// If file exists (ex. because a webhook was already created), skip creation.\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst managerWebhookPatchTemplate = `# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/config.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Config{}\n\n// Config scaffolds a file that defines the namespace and the manager deployment\ntype Config struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\t// Image is controller manager image name\n\tImage string\n\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Config) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"manager\", \"manager.yaml\")\n\t}\n\n\tf.TemplateBody = configTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst configTemplate = `apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: {{ .ProjectName }}\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: {{ .ProjectName }}\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: {{ .Image }}\n        name: manager\n{{- if .Namespaced }}\n        env:\n        - name: WATCH_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n{{- end }}\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/manager/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the manager folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"manager\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeManagerTemplate\n\n\tf.IfExistsAction = machinery.Error\n\n\treturn nil\n}\n\nconst kustomizeManagerTemplate = `resources:\n- manager.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-metrics-traffic.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage networkpolicy\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &PolicyAllowMetrics{}\n\n// PolicyAllowMetrics scaffolds a file that defines the NetworkPolicy\n// to allow access to the metrics endpoint\ntype PolicyAllowMetrics struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *PolicyAllowMetrics) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"network-policy\", \"allow-metrics-traffic.yaml\")\n\t}\n\n\tf.TemplateBody = metricsNetworkPolicyTemplate\n\n\treturn nil\n}\n\nconst metricsNetworkPolicyTemplate = `# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: {{ .ProjectName }}\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/allow-webhook-traffic.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage networkpolicy\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &PolicyAllowWebhooks{}\n\n// PolicyAllowWebhooks in scaffolds a file that defines the NetworkPolicy\n// to allow the webhook server can communicate\ntype PolicyAllowWebhooks struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *PolicyAllowWebhooks) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"network-policy\", \"allow-webhook-traffic.yaml\")\n\t}\n\n\tf.TemplateBody = webhooksNetworkPolicyTemplate\n\n\treturn nil\n}\n\nconst webhooksNetworkPolicyTemplate = `# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: {{ .ProjectName }}\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy/kustomization.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage networkpolicy\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"network-policy\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizationTemplate\n\n\treturn nil\n}\n\nconst kustomizationTemplate = `resources:\n- allow-metrics-traffic.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/prometheus/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage prometheus\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"prometheus\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizationTemplate\n\n\treturn nil\n}\n\nconst kustomizationTemplate = `resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/prometheus/monitor.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage prometheus\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Monitor{}\n\n// Monitor scaffolds a file that defines the prometheus service monitor\ntype Monitor struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Monitor) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"prometheus\", \"monitor.yaml\")\n\t}\n\n\tf.TemplateBody = serviceMonitorTemplate\n\n\treturn nil\n}\n\n//nolint:lll\nconst serviceMonitorTemplate = `# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: {{ .ProjectName }}\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/prometheus/monitor_tls_patch.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage prometheus\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ServiceMonitorPatch{}\n\n// ServiceMonitorPatch scaffolds a file that defines the patch for the ServiceMonitor\n// to use cert-manager managed certificates for secure TLS configuration.\ntype ServiceMonitorPatch struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ServiceMonitorPatch) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"prometheus\", \"monitor_tls_patch.yaml\")\n\t}\n\n\tf.TemplateBody = serviceMonitorPatchTemplate\n\n\treturn nil\n}\n\nconst serviceMonitorPatchTemplate = `# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/cluster_role.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ClusterRole{}\n\n// ClusterRole scaffolds a ClusterRole for the manager\ntype ClusterRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ClusterRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"role.yaml\")\n\t}\n\n\tf.TemplateBody = clusterRoleTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst clusterRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-role\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/cluster_role_binding.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ClusterRoleBinding{}\n\n// ClusterRoleBinding scaffolds a ClusterRoleBinding for the manager\ntype ClusterRoleBinding struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ClusterRoleBinding) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"role_binding.yaml\")\n\t}\n\n\tf.TemplateBody = clusterRoleBindingTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst clusterRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/crd_admin_role.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage rbac\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CRDAdminRole{}\n\n// CRDAdminRole scaffolds a file that defines the role that allows full control over plurals\ntype CRDAdminRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\tRoleName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CRDAdminRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[group]_%[kind]_admin_role.yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[kind]_admin_role.yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tif f.RoleName == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-%s-admin-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Group),\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t} else {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-admin-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t}\n\t}\n\n\tf.TemplateBody = crdRoleAdminTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst crdRoleAdminTemplate = `# This rule is not used by the project {{ .ProjectName }} itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over {{ .Resource.QualifiedGroup }}.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: {{ if .Namespaced }}Role{{ else }}ClusterRole{{ end }}\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ .RoleName }}\nrules:\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}\n  verbs:\n  - '*'\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}/status\n  verbs:\n  - get\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage rbac\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CRDEditorRole{}\n\n// CRDEditorRole scaffolds a file that defines the role that allows to edit plurals\ntype CRDEditorRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\tRoleName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CRDEditorRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[group]_%[kind]_editor_role.yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[kind]_editor_role.yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tif f.RoleName == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-%s-editor-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Group),\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t} else {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-editor-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t}\n\t}\n\n\tf.TemplateBody = crdRoleEditorTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst crdRoleEditorTemplate = `# This rule is not used by the project {{ .ProjectName }} itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the {{ .Resource.QualifiedGroup }}.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: {{ if .Namespaced }}Role{{ else }}ClusterRole{{ end }}\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ .RoleName }}\nrules:\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}/status\n  verbs:\n  - get\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage rbac\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CRDViewerRole{}\n\n// CRDViewerRole scaffolds a file that defines the role that allows to view plurals\ntype CRDViewerRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\tRoleName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CRDViewerRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[group]_%[kind]_viewer_role.yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"%[kind]_viewer_role.yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tif f.RoleName == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-%s-viewer-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Group),\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t} else {\n\t\t\tf.RoleName = fmt.Sprintf(\"%s-viewer-role\",\n\t\t\t\tstrings.ToLower(f.Resource.Kind))\n\t\t}\n\t}\n\n\tf.TemplateBody = crdRoleViewerTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst crdRoleViewerTemplate = `# This rule is not used by the project {{ .ProjectName }} itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to {{ .Resource.QualifiedGroup }} resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: {{ if .Namespaced }}Role{{ else }}ClusterRole{{ end }}\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ .RoleName }}\nrules:\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - {{ .Resource.QualifiedGroup }}\n  resources:\n  - {{ .Resource.Plural }}/status\n  verbs:\n  - get\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the rbac folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeRBACTemplate\n\n\tf.IfExistsAction = machinery.Error\n\n\treturn nil\n}\n\nconst kustomizeRBACTemplate = `resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &LeaderElectionRole{}\n\n// LeaderElectionRole scaffolds a file that defines the role that allows leader election\ntype LeaderElectionRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *LeaderElectionRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"leader_election_role.yaml\")\n\t}\n\n\tf.TemplateBody = leaderElectionRoleTemplate\n\n\treturn nil\n}\n\nconst leaderElectionRoleTemplate = `# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &LeaderElectionRoleBinding{}\n\n// LeaderElectionRoleBinding scaffolds a file that defines the role binding that allows leader election\ntype LeaderElectionRoleBinding struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *LeaderElectionRoleBinding) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"leader_election_role_binding.yaml\")\n\t}\n\n\tf.TemplateBody = leaderElectionRoleBindingTemplate\n\n\treturn nil\n}\n\nconst leaderElectionRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/metrics_auth_role.go",
    "content": "/*\n Copyright 2020 The Kubernetes Authors.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n     http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &MetricsAuthRole{}\n\n// MetricsAuthRole scaffolds a file that defines the role for the auth proxy\ntype MetricsAuthRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *MetricsAuthRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"metrics_auth_role.yaml\")\n\t}\n\n\tf.TemplateBody = metricsAuthRoleTemplate\n\n\treturn nil\n}\n\nconst metricsAuthRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/metrics_auth_role_binding.go",
    "content": "/*\n Copyright 2020 The Kubernetes Authors.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n     http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &MetricsAuthRole{}\n\n// MetricsAuthRoleBinding scaffolds a file that defines the role binding for the auth proxy\ntype MetricsAuthRoleBinding struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *MetricsAuthRoleBinding) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"metrics_auth_role_binding.yaml\")\n\t}\n\n\tf.TemplateBody = metricsAuthRoleBindingTemplate\n\n\treturn nil\n}\n\nconst metricsAuthRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/metrics_reader_role.go",
    "content": "/*\n Copyright 2020 The Kubernetes Authors.\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n     http://www.apache.org/licenses/LICENSE-2.0\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &MetricsReaderRole{}\n\n// MetricsReaderRole scaffolds a file that defines the role for the auth proxy\ntype MetricsReaderRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *MetricsReaderRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"metrics_reader_role.yaml\")\n\t}\n\n\tf.TemplateBody = metricsReaderRoleTemplate\n\n\treturn nil\n}\n\nconst metricsReaderRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/namespaced_role.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &NamespacedRole{}\n\n// NamespacedRole scaffolds a namespace-scoped Role for the manager\ntype NamespacedRole struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *NamespacedRole) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"role.yaml\")\n\t}\n\n\tf.TemplateBody = namespacedRoleTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst namespacedRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-role\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/namespaced_role_binding.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &NamespacedRoleBinding{}\n\n// NamespacedRoleBinding scaffolds a namespace-scoped RoleBinding for the manager\ntype NamespacedRoleBinding struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *NamespacedRoleBinding) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"role_binding.yaml\")\n\t}\n\n\tf.TemplateBody = namespacedRoleBindingTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst namespacedRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/rbac/service_account.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage rbac\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ServiceAccount{}\n\n// ServiceAccount scaffolds a file that defines the service account the manager is deployed in.\ntype ServiceAccount struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ServiceAccount) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"rbac\", \"service_account.yaml\")\n\t}\n\n\tf.TemplateBody = serviceAccountTemplate\n\n\treturn nil\n}\n\nconst serviceAccountTemplate = `apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/samples/crd_sample.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage samples\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CRDSample{}\n\n// CRDSample scaffolds a file that defines a sample manifest for the CRD\ntype CRDSample struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CRDSample) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"samples\", \"%[group]_%[version]_%[kind].yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"samples\", \"%[version]_%[kind].yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.Error\n\t}\n\n\tf.TemplateBody = crdSampleTemplate\n\n\treturn nil\n}\n\nconst crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }}\nkind: {{ .Resource.Kind }}\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ lower .Resource.Kind }}-sample\nspec:\n  # TODO(user): Add fields here\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/samples/kustomization.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage samples\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar (\n\t_ machinery.Template = &Kustomization{}\n\t_ machinery.Inserter = &Kustomization{}\n)\n\n// Kustomization scaffolds a kustomization.yaml for the manifests overlay folder.\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"samples\", \"kustomization.yaml\")\n\t}\n\tf.TemplateBody = fmt.Sprintf(kustomizationTemplate, machinery.NewMarkerFor(f.Path, samplesMarker))\n\n\treturn nil\n}\n\nconst (\n\tsamplesMarker = \"manifestskustomizesamples\"\n)\n\n// GetMarkers implements file.Inserter\nfunc (f *Kustomization) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{machinery.NewMarkerFor(f.Path, samplesMarker)}\n}\n\nconst samplesCodeFragment = `- %s\n`\n\n// makeCRFileName returns a Custom Resource example file name in the same format\n// as kubebuilder's CreateAPI plugin for a gvk.\nfunc (f Kustomization) makeCRFileName() string {\n\tif f.Resource.Group != \"\" {\n\t\treturn f.Resource.Replacer().Replace(\"%[group]_%[version]_%[kind].yaml\")\n\t}\n\treturn f.Resource.Replacer().Replace(\"%[version]_%[kind].yaml\")\n}\n\n// GetCodeFragments implements file.Inserter\nfunc (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap {\n\treturn machinery.CodeFragmentsMap{\n\t\tmachinery.NewMarkerFor(f.Path, samplesMarker): []string{fmt.Sprintf(samplesCodeFragment, f.makeCRFileName())},\n\t}\n}\n\nconst kustomizationTemplate = `## Append samples of your project ##\nresources:\n%s\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/webhook/kustomization.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhook\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Kustomization{}\n\n// Kustomization scaffolds a file that defines the kustomization scheme for the webhook folder\ntype Kustomization struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Kustomization) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"webhook\", \"kustomization.yaml\")\n\t}\n\n\tf.TemplateBody = kustomizeWebhookTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\t// If file exists (ex. because a webhook was already created), skip creation.\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst kustomizeWebhookTemplate = `resources:\n- manifests{{ if ne .Resource.Webhooks.WebhookVersion \"v1\" }}.{{ .Resource.Webhooks.WebhookVersion }}{{ end }}.yaml\n- service.yaml\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/webhook/service.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhook\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Service{}\n\n// Service scaffolds a file that defines the webhook service\ntype Service struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Service) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"config\", \"webhook\", \"service.yaml\")\n\t}\n\n\tf.TemplateBody = serviceTemplate\n\n\t// If file exists (ex. because a webhook was already created), skip creation.\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst serviceTemplate = `apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: {{ .ProjectName }}\n`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/scaffolds/webhook.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/certmanager\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/crd/patches\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/kdefault\"\n\tnetworkpolicy \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/network-policy\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds/internal/templates/config/webhook\"\n)\n\nvar _ plugins.Scaffolder = &webhookScaffolder{}\n\nconst (\n\tkustomizeFilePath    = \"config/default/kustomization.yaml\"\n\tkustomizeCRDFilePath = \"config/crd/kustomization.yaml\"\n)\n\ntype webhookScaffolder struct {\n\tconfig   config.Config\n\tresource resource.Resource\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n\n\t// force indicates whether to scaffold files even if they exist.\n\tforce bool\n}\n\n// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations\nfunc NewWebhookScaffolder(cfg config.Config, res resource.Resource, force bool) plugins.Scaffolder {\n\treturn &webhookScaffolder{\n\t\tconfig:   cfg,\n\t\tresource: res,\n\t\tforce:    force,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) { s.fs = fs }\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *webhookScaffolder) Scaffold() error {\n\tlog.Info(\"Writing kustomize manifests for you to edit...\")\n\n\t// Will validate the scaffold\n\t// Users that scaffolded the project previously\n\t// with the bugs will receive a message to help\n\t// them out fix their scaffold.\n\tvalidateScaffoldedProject()\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t\tmachinery.WithResource(&s.resource),\n\t)\n\n\tif err := s.config.UpdateResource(s.resource); err != nil {\n\t\treturn fmt.Errorf(\"error updating resource: %w\", err)\n\t}\n\n\tbuildScaffold := []machinery.Builder{\n\t\t&kdefault.ManagerWebhookPatch{},\n\t\t&webhook.Kustomization{Force: s.force},\n\t\t&webhook.Service{},\n\t\t&certmanager.Certificate{},\n\t\t&certmanager.Issuer{},\n\t\t&certmanager.MetricsCertificate{},\n\t\t&certmanager.Kustomization{},\n\t\t&certmanager.KustomizeConfig{},\n\t\t&networkpolicy.PolicyAllowWebhooks{},\n\t}\n\n\t// Only scaffold the following patches if is a conversion webhook\n\tif s.resource.Webhooks.Conversion {\n\t\tbuildScaffold = append(buildScaffold, &patches.EnableWebhookPatch{})\n\t\tbuildScaffold = append(buildScaffold, &kdefault.KustomizationCAConversionUpdater{})\n\t}\n\n\tif !s.resource.External && !s.resource.Core {\n\t\tbuildScaffold = append(buildScaffold, &crd.Kustomization{})\n\t}\n\n\tif err := scaffold.Execute(buildScaffold...); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding kustomize webhook manifests: %w\", err)\n\t}\n\n\t// Warn users about potential bootstrap problem for core type webhooks\n\tif s.resource.Core {\n\t\tlog.Warn(\"Webhooks for core types may cause circular dependencies during deployment. \" +\n\t\t\t\"More info: https://book.kubebuilder.io/reference/webhook-bootstrap-problem\")\n\t}\n\n\t// Apply project-specific customizations:\n\t// - Add reference to allow-webhook-traffic.yaml in network policy configuration.\n\t// - Enable all webhook-related sections in config/default/kustomization.yaml.\n\taddNetworkPoliciesForWebhooks()\n\t// enableWebhookDefaults ensures all necessary components for webhook functionality\n\t// are enabled in config/default/kustomization.yaml, including:\n\t// - webhook and cert-manager directories\n\t// - manager patches\n\t// - replacements for certificate injection\n\tenableWebhookDefaults()\n\tif s.resource.HasValidationWebhook() {\n\t\tuncommentCodeForValidationWebhooks()\n\t}\n\tif s.resource.HasDefaultingWebhook() {\n\t\tuncommentCodeForDefaultWebhooks()\n\t}\n\tif s.resource.HasConversionWebhook() {\n\t\tuncommentCodeForConversionWebhooks(s.resource)\n\t}\n\n\tconst helmPluginKey = \"helm.kubebuilder.io/v1-alpha\"\n\tvar helmPlugin any\n\terr := s.config.DecodePluginConfig(helmPluginKey, &helmPlugin)\n\tif !errors.As(err, &config.PluginKeyNotFoundError{}) {\n\t\ttestChartPath := \".github/workflows/test-chart.yml\"\n\t\t//nolint:lll\n\t\t_ = pluginutil.UncommentCode(\n\t\t\ttestChartPath, `#      - name: Install cert-manager via Helm\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true\n#\n#      - name: Wait for cert-manager to be ready\n#        run: |\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook\n`, \"#\",\n\t\t)\n\n\t\t_ = pluginutil.ReplaceInFile(testChartPath, \"# TODO: Uncomment if cert-manager is enabled\", \"\")\n\t}\n\n\treturn nil\n}\n\n// uncommentCodeForConversionWebhooks enables CA injection logic in Kustomize manifests\n// for ConversionWebhooks by uncommenting certificate sources and CRD annotation targets.\n// This is required to make cert-manager correctly inject the CA bundle into CRDs.\nfunc uncommentCodeForConversionWebhooks(r resource.Resource) {\n\tcrdName := fmt.Sprintf(\"%s.%s\", r.Plural, r.QualifiedGroup())\n\terr := pluginutil.UncommentCode(\n\t\tkustomizeFilePath,\n\t\tfmt.Sprintf(`# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n#     - select:\n#         kind: CustomResourceDefinition\n#         name: %s\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true`, crdName),\n\t\t\"#\",\n\t)\n\tif err != nil {\n\t\tlog.Warn(\"Unable to find the certificate namespace replacement for \"+\n\t\t\t\"CRD to uncomment in the file. Conversion webhooks require this replacement \"+\n\t\t\t\"to inject the CA properly.\",\n\t\t\t\"crdName\", crdName, \"file\", kustomizeFilePath)\n\t}\n\terr = pluginutil.UncommentCode(\n\t\tkustomizeFilePath,\n\t\tfmt.Sprintf(`# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n#     - select:\n#         kind: CustomResourceDefinition\n#         name: %s\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true`, crdName),\n\t\t\"#\",\n\t)\n\tif err != nil {\n\t\tlog.Warn(\"Unable to find the certificate name replacement for CRD \"+\n\t\t\t\"to uncomment in the file. Conversion webhooks require this replacement to inject \"+\n\t\t\t\"the CA properly.\",\n\t\t\t\"crdName\", crdName, \"file\", kustomizeFilePath)\n\t}\n\n\terr = pluginutil.UncommentCode(kustomizeCRDFilePath, `#configurations:\n#- kustomizeconfig.yaml`, `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeCRDFilePath,\n\t\t\t`configurations:\n- kustomizeconfig.yaml`)\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the target configurations with kustomizeconfig.yaml \"+\n\t\t\t\t\"to uncomment in the file; conversion webhooks require this configuration \"+\n\t\t\t\t\"to be uncommented to inject CA\", \"file\", kustomizeCRDFilePath)\n\t\t}\n\t}\n}\n\nfunc uncommentCodeForDefaultWebhooks() {\n\terr := pluginutil.UncommentCode(\n\t\tkustomizeFilePath,\n\t\t`# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true`,\n\t\t\"#\",\n\t)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t`   targets:\n     - select:\n         kind: MutatingWebhookConfiguration`)\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the MutatingWebhookConfiguration section \"+\n\t\t\t\t\"to uncomment in the file. Webhooks scaffolded with '--defaulting' require \"+\n\t\t\t\t\"this configuration for CA injection\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n}\n\nfunc uncommentCodeForValidationWebhooks() {\n\terr := pluginutil.UncommentCode(\n\t\tkustomizeFilePath,\n\t\t`# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert # This name should match the one in certificate.yaml\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: ValidatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true`,\n\t\t\"#\",\n\t)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t`   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration`)\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the ValidatingWebhookConfiguration section \"+\n\t\t\t\t\"to uncomment in the file. Webhooks scaffolded with '--programmatic-validation' \"+\n\t\t\t\t\"require this configuration for CA injection\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n}\n\nfunc enableWebhookDefaults() {\n\terr := pluginutil.UncommentCode(kustomizeFilePath, \"#- ../webhook\", `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, \"- ../webhook\")\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the target #- ../webhook to uncomment in the file\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n\n\terr = pluginutil.UncommentCode(kustomizeFilePath, \"#patches:\", `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath, \"patches:\")\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the line '#patches:' to uncomment in the file\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n\n\terr = pluginutil.UncommentCode(kustomizeFilePath, `#- path: manager_webhook_patch.yaml\n#  target:\n#    kind: Deployment`, `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t\"- path: manager_webhook_patch.yaml\")\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the target #- path: manager_webhook_patch.yaml to uncomment in the file\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n\n\terr = pluginutil.UncommentCode(kustomizeFilePath, `#- ../certmanager`, `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t\"../certmanager\")\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"unable to find the '../certmanager' section to uncomment in the file. \"+\n\t\t\t\t\"Projects that use webhooks must enable certificate management; \"+\n\t\t\t\t\"Please ensure cert-manager integration is enabled\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n\n\terr = pluginutil.UncommentCode(kustomizeFilePath, `#replacements:`, `#`)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t\"replacements:\")\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"Unable to find the '#replacements:' section to uncomment in the file\"+\n\t\t\t\t\"Projects using webhooks must enable cert-manager CA injection by uncommenting\"+\n\t\t\t\t\"the required replacements.\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n\n\terr = pluginutil.UncommentCode(\n\t\tkustomizeFilePath,\n\t\t`# - source: # Uncomment the following block if you have any webhook\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.name # Name of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Service\n#     version: v1\n#     name: webhook-service\n#     fieldPath: .metadata.namespace # Namespace of the service\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: serving-cert\n#       fieldPaths:\n#         - .spec.dnsNames.0\n#         - .spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true`,\n\t\t\"#\",\n\t)\n\tif err != nil {\n\t\thasWebHookUncommented, errCheck := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\t`     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name`)\n\t\tif !hasWebHookUncommented || errCheck != nil {\n\t\t\tlog.Warn(\"Unable to find the '#- source: # Uncomment the following block if you have any webhook' \"+\n\t\t\t\t\"section to uncomment in the file. \"+\n\t\t\t\t\"Projects with webhooks must enable certificates via cert-manager.\",\n\t\t\t\t\"file\", kustomizeFilePath)\n\t\t}\n\t}\n}\n\nfunc addNetworkPoliciesForWebhooks() {\n\tpolicyKustomizeFilePath := \"config/network-policy/kustomization.yaml\"\n\terr := pluginutil.InsertCodeIfNotExist(policyKustomizeFilePath,\n\t\t\"resources:\", allowWebhookTrafficFragment)\n\tif err != nil {\n\t\tlog.Error(\"failed to add the line '- allow-webhook-traffic.yaml' at the end of the file \"+\n\t\t\t\"to allow webhook traffic\", \"file\", policyKustomizeFilePath)\n\t}\n}\n\n// Deprecated: remove it when go/v4 and/or kustomize/v2 be removed\n// validateScaffoldedProject will output a message to help users fix their scaffold\nfunc validateScaffoldedProject() {\n\thasCertManagerPatch, _ := pluginutil.HasFileContentWith(kustomizeFilePath,\n\t\t\"crdkustomizecainjectionpatch\")\n\n\tif hasCertManagerPatch {\n\t\tlog.Warn(`\n\n1. **Remove the CERTMANAGER Section from config/crd/kustomization.yaml:**\n\n   Delete the CERTMANAGER section to prevent unintended CA injection patches for CRDs.\n   Ensure the following lines are removed or commented out:\n\n   # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.\n   # patches here are for enabling the CA injection for each CRD\n   #- path: patches/cainjection_in_firstmates.yaml\n   # +kubebuilder:scaffold:crdkustomizecainjectionpatch\n\n2. **Ensure CA Injection Configuration in config/default/kustomization.yaml:**\n\n   Under the [CERTMANAGER] replacement in config/default/kustomization.yaml,\n   add the following code for proper CA injection generation:\n\n   **NOTE:** You must ensure that the code contains the following target markers:\n   - +kubebuilder:scaffold:crdkustomizecainjectionns\n   - +kubebuilder:scaffold:crdkustomizecainjectioname\n\n   # - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n   #     kind: Certificate\n   #     group: cert-manager.io\n   #     version: v1\n   #     name: serving-cert # This name should match the one in certificate.yaml\n   #     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   #   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n   # +kubebuilder:scaffold:crdkustomizecainjectionns\n   # - source:\n   #     kind: Certificate\n   #     group: cert-manager.io\n   #     version: v1\n   #     name: serving-cert # This name should match the one in certificate.yaml\n   #     fieldPath: .metadata.name\n   #   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n   # +kubebuilder:scaffold:crdkustomizecainjectioname\n\n3. **Ensure Only Conversion Webhook Patches in config/crd/patches:**\n\n   The config/crd/patches directory and the corresponding entries in config/crd/kustomization.yaml should only \n   contain files for conversion webhooks. Previously, a bug caused the patch file to be generated for any webhook, \n   but only patches for webhooks created with the --conversion option should be included.\n\nFor further guidance, you can refer to examples in the testdata/ directory in the Kubebuilder repository.\n\n**Alternatively**: You can use the 'alpha generate' command to re-generate the project from scratch using the latest\nrelease available. Afterward, you can re-add only your code implementation on top to ensure your project includes all\nthe latest bug fixes and enhancements.\n\n`)\n\t}\n}\n\nconst allowWebhookTrafficFragment = `\n- allow-webhook-traffic.yaml`\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestKustomizeV2Plugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Kustomize V2 Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/common/kustomize/v2/webhook.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds\"\n)\n\nvar _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}\n\ntype createWebhookSubcommand struct {\n\tcreateSubcommand\n}\n\nfunc (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold webhook subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/domain.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugins\n\n// DefaultNameQualifier is the suffix appended to all kubebuilder plugin names.\nconst DefaultNameQualifier = \"kubebuilder.io\"\n"
  },
  {
    "path": "pkg/plugins/external/api.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar _ plugin.CreateAPISubcommand = &createAPISubcommand{}\n\nconst (\n\tdefaultAPIVersion = \"v1alpha1\"\n)\n\ntype createAPISubcommand struct {\n\tPath        string\n\tArgs        []string\n\tpluginChain []string\n\tconfig      config.Config\n}\n\n// InjectConfig injects the project configuration so external plugins can read the PROJECT file.\nfunc (p *createAPISubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tif chain := c.GetPluginChain(); len(chain) > 0 {\n\t\tp.pluginChain = append([]string(nil), chain...)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) SetPluginChain(chain []string) {\n\tif len(chain) == 0 {\n\t\tp.pluginChain = nil\n\t\treturn\n\t}\n\n\tp.pluginChain = append([]string(nil), chain...)\n}\n\nfunc (p *createAPISubcommand) InjectResource(*resource.Resource) error {\n\t// Do nothing since resource flags are passed to the external plugin directly.\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsetExternalPluginMetadata(\"api\", p.Path, subcmdMeta)\n}\n\nfunc (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {\n\tbindExternalPluginFlags(fs, \"api\", p.Path, p.Args)\n}\n\nfunc (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {\n\treq := external.PluginRequest{\n\t\tAPIVersion:  defaultAPIVersion,\n\t\tCommand:     \"create api\",\n\t\tArgs:        p.Args,\n\t\tPluginChain: p.pluginChain,\n\t}\n\n\terr := handlePluginResponse(fs, req, p.Path, p.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/external/edit.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage external\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tPath        string\n\tArgs        []string\n\tpluginChain []string\n\tconfig      config.Config\n}\n\n// InjectConfig injects the project configuration to access plugin chain information\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tif chain := c.GetPluginChain(); len(chain) > 0 {\n\t\tp.pluginChain = append([]string(nil), chain...)\n\t}\n\n\treturn nil\n}\n\nfunc (p *editSubcommand) SetPluginChain(chain []string) {\n\tif len(chain) == 0 {\n\t\tp.pluginChain = nil\n\t\treturn\n\t}\n\n\tp.pluginChain = append([]string(nil), chain...)\n}\n\nfunc (p *editSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsetExternalPluginMetadata(\"edit\", p.Path, subcmdMeta)\n}\n\nfunc (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tbindExternalPluginFlags(fs, \"edit\", p.Path, p.Args)\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\treq := external.PluginRequest{\n\t\tAPIVersion:  defaultAPIVersion,\n\t\tCommand:     \"edit\",\n\t\tArgs:        p.Args,\n\t\tPluginChain: p.pluginChain,\n\t}\n\n\terr := handlePluginResponse(fs, req, p.Path, p.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/external/external_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\ntype chainAwareSubcommand interface {\n\tSetPluginChain([]string)\n\tInjectConfig(config.Config) error\n}\n\nvar pluginChainTestCases = []struct {\n\tname string\n\tnew  func() chainAwareSubcommand\n\tget  func(chainAwareSubcommand) []string\n}{\n\t{\n\t\tname: \"init\",\n\t\tnew:  func() chainAwareSubcommand { return &initSubcommand{} },\n\t\tget:  func(sub chainAwareSubcommand) []string { return sub.(*initSubcommand).pluginChain },\n\t},\n\t{\n\t\tname: \"edit\",\n\t\tnew:  func() chainAwareSubcommand { return &editSubcommand{} },\n\t\tget:  func(sub chainAwareSubcommand) []string { return sub.(*editSubcommand).pluginChain },\n\t},\n\t{\n\t\tname: \"create api\",\n\t\tnew:  func() chainAwareSubcommand { return &createAPISubcommand{} },\n\t\tget:  func(sub chainAwareSubcommand) []string { return sub.(*createAPISubcommand).pluginChain },\n\t},\n\t{\n\t\tname: \"create webhook\",\n\t\tnew:  func() chainAwareSubcommand { return &createWebhookSubcommand{} },\n\t\tget:  func(sub chainAwareSubcommand) []string { return sub.(*createWebhookSubcommand).pluginChain },\n\t},\n}\n\nfunc TestExternalPlugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Scaffold\")\n}\n\ntype mockValidOutputGetter struct{}\n\ntype mockInValidOutputGetter struct{}\n\nvar _ ExecOutputGetter = &mockValidOutputGetter{}\n\nfunc (m *mockValidOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, error) {\n\treturn []byte(`{\n\t\t\"command\": \"init\", \n\t\t\"error\": false, \n\t\t\"error_msg\": \"none\", \n\t\t\"universe\": {\"LICENSE\": \"Apache 2.0 License\\n\"}\n\t\t}`), nil\n}\n\nvar _ ExecOutputGetter = &mockInValidOutputGetter{}\n\nfunc (m *mockInValidOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, error) {\n\treturn nil, fmt.Errorf(\"error getting exec command output\")\n}\n\ntype mockPluginChainCaptureGetter struct {\n\tcapturedChain *[]string\n}\n\nvar _ ExecOutputGetter = &mockPluginChainCaptureGetter{}\n\nfunc (m *mockPluginChainCaptureGetter) GetExecOutput(request []byte, _ string) ([]byte, error) {\n\t// Parse the request to capture the plugin chain\n\tvar req external.PluginRequest\n\tif err := json.Unmarshal(request, &req); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling request: %w\", err)\n\t}\n\n\t// Capture the plugin chain\n\t*m.capturedChain = req.PluginChain\n\n\t// Return a valid response\n\treturn []byte(`{\n\t\t\"command\": \"init\",\n\t\t\"error\": false,\n\t\t\"error_msg\": \"none\",\n\t\t\"universe\": {\"LICENSE\": \"Apache 2.0 License\\n\"}\n\t}`), nil\n}\n\ntype mockValidOsWdGetter struct{}\n\nvar _ OsWdGetter = &mockValidOsWdGetter{}\n\nfunc (m *mockValidOsWdGetter) GetCurrentDir() (string, error) {\n\treturn \"tmp/externalPlugin\", nil\n}\n\ntype mockInValidOsWdGetter struct{}\n\nvar _ OsWdGetter = &mockInValidOsWdGetter{}\n\nfunc (m *mockInValidOsWdGetter) GetCurrentDir() (string, error) {\n\treturn \"\", fmt.Errorf(\"error getting current directory\")\n}\n\ntype mockConfigOutputGetter struct {\n\tcapturedRequest *external.PluginRequest\n}\n\nvar _ ExecOutputGetter = &mockConfigOutputGetter{}\n\nfunc (m *mockConfigOutputGetter) GetExecOutput(reqBytes []byte, _ string) ([]byte, error) {\n\tm.capturedRequest = &external.PluginRequest{}\n\tif err := json.Unmarshal(reqBytes, m.capturedRequest); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling request: %w\", err)\n\t}\n\n\treturn []byte(`{\n\t\t\"command\": \"init\", \n\t\t\"error\": false, \n\t\t\"error_msg\": \"none\", \n\t\t\"universe\": {\"LICENSE\": \"Apache 2.0 License\\n\"}\n\t\t}`), nil\n}\n\ntype mockValidFlagOutputGetter struct{}\n\nfunc (m *mockValidFlagOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, error) {\n\tresponse := external.PluginResponse{\n\t\tCommand:  \"flag\",\n\t\tError:    false,\n\t\tUniverse: nil,\n\t\tFlags:    getFlags(),\n\t}\n\tmarshaledResponse, err := json.Marshal(response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshalling response: %w\", err)\n\t}\n\n\treturn marshaledResponse, nil\n}\n\ntype mockValidMEOutputGetter struct{}\n\nfunc (m *mockValidMEOutputGetter) GetExecOutput(_ []byte, _ string) ([]byte, error) {\n\tresponse := external.PluginResponse{\n\t\tCommand:  \"metadata\",\n\t\tError:    false,\n\t\tUniverse: nil,\n\t\tMetadata: getMetadata(),\n\t}\n\n\tmarshaledResponse, err := json.Marshal(response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshalling response: %w\", err)\n\t}\n\n\treturn marshaledResponse, nil\n}\n\nconst (\n\texternalPlugin = \"myexternalplugin.sh\"\n\tfloatVal       = \"float\"\n)\n\nvar _ = Describe(\"Run external plugin using Scaffold\", func() {\n\tContext(\"with valid mock values\", func() {\n\t\tconst filePerm os.FileMode = 755\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\targs           []string\n\t\t\tf              afero.File\n\t\t\tfs             machinery.Filesystem\n\n\t\t\terr error\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\t\t\tfs = machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tpluginFileName = \"externalPlugin.sh\"\n\t\t\tpluginFilePath := filepath.Join(\"tmp\", \"externalPlugin\", pluginFileName)\n\n\t\t\terr = fs.FS.MkdirAll(filepath.Dir(pluginFilePath), filePerm)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tf, err = fs.FS.Create(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t_, err = fs.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\targs = []string{\"--domain\", \"example.com\"}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tfilename := filepath.Join(\"tmp\", \"externalPlugin\", \"LICENSE\")\n\t\t\tvar fileInfo os.FileInfo\n\t\t\tfileInfo, err = fs.FS.Stat(filename)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(fileInfo).NotTo(BeNil())\n\t\t})\n\n\t\tIt(\"should successfully run init subcommand on the external plugin\", func() {\n\t\t\ti := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = i.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should successfully run edit subcommand on the external plugin\", func() {\n\t\t\te := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = e.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should successfully run create api subcommand on the external plugin\", func() {\n\t\t\tc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should successfully run create webhook subcommand on the external plugin\", func() {\n\t\t\tc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t})\n\t})\n\n\tContext(\"with invalid mock values of GetExecOutput() and GetCurrentDir()\", func() {\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\targs           []string\n\t\t\tfs             machinery.Filesystem\n\t\t\terr            error\n\t\t)\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockInValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\t\t\tfs = machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tpluginFileName = externalPlugin\n\t\t\targs = []string{\"--domain\", \"example.com\"}\n\t\t})\n\n\t\tIt(\"should return error upon running init subcommand on the external plugin\", func() {\n\t\t\ti := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = i.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting exec command output\"))\n\n\t\t\toutputGetter = &mockValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockInValidOsWdGetter{}\n\n\t\t\terr = i.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting current directory\"))\n\t\t})\n\n\t\tIt(\"should return error upon running edit subcommand on the external plugin\", func() {\n\t\t\te := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = e.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting exec command output\"))\n\n\t\t\toutputGetter = &mockValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockInValidOsWdGetter{}\n\n\t\t\terr = e.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting current directory\"))\n\t\t})\n\n\t\tIt(\"should return error upon running create api subcommand on the external plugin\", func() {\n\t\t\tc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting exec command output\"))\n\n\t\t\toutputGetter = &mockValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockInValidOsWdGetter{}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting current directory\"))\n\t\t})\n\n\t\tIt(\"should return error upon running create webhook subcommand on the external plugin\", func() {\n\t\t\tc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting exec command output\"))\n\n\t\t\toutputGetter = &mockValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockInValidOsWdGetter{}\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"error getting current directory\"))\n\t\t})\n\t})\n\n\tContext(\"with successfully getting flags from external plugin\", func() {\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\targs           []string\n\t\t\tflagset        *pflag.FlagSet\n\n\t\t\t// Make an array of flags to represent the ones that should be returned in these tests\n\t\t\tflags []external.Flag\n\n\t\t\tcheckFlagset func()\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockValidFlagOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\n\t\t\tpluginFileName = externalPlugin\n\t\t\targs = []string{\"--captain\", \"black-beard\", \"--sail\"}\n\t\t\tflagset = pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\n\t\t\tflags = getFlags()\n\n\t\t\tcheckFlagset = func() {\n\t\t\t\tExpect(flagset.HasFlags()).To(BeTrue())\n\n\t\t\t\tfor _, flag := range flags {\n\t\t\t\t\tExpect(flagset.Lookup(flag.Name)).NotTo(BeNil())\n\t\t\t\t\t// we parse floats as float64 Go type so this check will account for that\n\t\t\t\t\tif flag.Type != floatVal {\n\t\t\t\t\t\tExpect(flagset.Lookup(flag.Name).Value.Type()).To(Equal(flag.Type))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(flagset.Lookup(flag.Name).Value.Type()).To(Equal(\"float64\"))\n\t\t\t\t\t}\n\t\t\t\t\tExpect(flagset.Lookup(flag.Name).Usage).To(Equal(flag.Usage))\n\t\t\t\t\tExpect(flagset.Lookup(flag.Name).DefValue).To(Equal(flag.Default))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should successfully bind external plugin specified flags for `init` subcommand\", func() {\n\t\t\tsc := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind external plugin specified flags for `create api` subcommand\", func() {\n\t\t\tsc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind external plugin specified  flags for `create webhook` subcommand\", func() {\n\t\t\tsc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind external plugin specified flags for `edit` subcommand\", func() {\n\t\t\tsc := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\t})\n\n\tContext(\"with failure to get flags from external plugin\", func() {\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\targs           []string\n\t\t\tflagset        *pflag.FlagSet\n\t\t\tusage          string\n\t\t\tcheckFlagset   func()\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockInValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\n\t\t\tpluginFileName = externalPlugin\n\t\t\targs = []string{\"--captain\", \"black-beard\", \"--sail\"}\n\t\t\tflagset = pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\t\t\tusage = \"Kubebuilder could not validate this flag with the external plugin. \" +\n\t\t\t\t\"Consult the external plugin documentation for more information.\"\n\n\t\t\tcheckFlagset = func() {\n\t\t\t\tExpect(flagset.HasFlags()).To(BeTrue())\n\n\t\t\t\tExpect(flagset.Lookup(\"captain\")).NotTo(BeNil())\n\t\t\t\tExpect(flagset.Lookup(\"captain\").Value.Type()).To(Equal(\"string\"))\n\t\t\t\tExpect(flagset.Lookup(\"captain\").Usage).To(Equal(usage))\n\n\t\t\t\tExpect(flagset.Lookup(\"sail\")).NotTo(BeNil())\n\t\t\t\tExpect(flagset.Lookup(\"sail\").Value.Type()).To(Equal(\"bool\"))\n\t\t\t\tExpect(flagset.Lookup(\"sail\").Usage).To(Equal(usage))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should successfully bind all user passed flags for `init` subcommand\", func() {\n\t\t\tsc := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind all user passed flags for `create api` subcommand\", func() {\n\t\t\tsc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind all user passed flags for `create webhook` subcommand\", func() {\n\t\t\tsc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\n\t\tIt(\"should successfully bind all user passed flags for `edit` subcommand\", func() {\n\t\t\tsc := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tsc.BindFlags(flagset)\n\n\t\t\tcheckFlagset()\n\t\t})\n\t})\n\n\tContext(\"Flag Parsing Filter Functions\", func() {\n\t\tIt(\"gvk(Arg/Flag)Filter should filter out (--)group, (--)version, (--)kind\", func() {\n\t\t\tfor _, toBeFiltered := range []string{\n\t\t\t\t\"group\", \"version\", \"kind\",\n\t\t\t} {\n\t\t\t\tExpect(gvkArgFilter(\"--\" + toBeFiltered)).To(BeFalse())\n\t\t\t\tExpect(gvkArgFilter(toBeFiltered)).To(BeFalse())\n\t\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"--\" + toBeFiltered})).To(BeFalse())\n\t\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"--\" + toBeFiltered})).To(BeFalse())\n\t\t\t}\n\t\t\tExpect(gvkArgFilter(\"somerandomflag\")).To(BeTrue())\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"somerandomflag\"})).To(BeTrue())\n\t\t})\n\n\t\tIt(\"helpArgFilter should filter out (--)help\", func() {\n\t\t\tExpect(helpArgFilter(\"--help\")).To(BeFalse())\n\t\t\tExpect(helpArgFilter(\"help\")).To(BeFalse())\n\t\t\tExpect(helpArgFilter(\"somerandomflag\")).To(BeTrue())\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"--help\"})).To(BeFalse())\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"help\"})).To(BeFalse())\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"somerandomflag\"})).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"Flag Parsing Helper Functions\", func() {\n\t\tvar (\n\t\t\tfs                  *pflag.FlagSet\n\t\t\targs                []string\n\t\t\tforbidden           []string\n\t\t\tflags               []external.Flag\n\t\t\targFilters          []argFilterFunc\n\t\t\texternalFlagFilters []externalFlagFilterFunc\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\targs = []string{\n\t\t\t\t\"--domain\", \"something.com\",\n\t\t\t\t\"--boolean\",\n\t\t\t\t\"--another\", \"flag\",\n\t\t\t\t\"--help\",\n\t\t\t\t\"--group\", \"somegroup\",\n\t\t\t\t\"--kind\", \"somekind\",\n\t\t\t\t\"--version\", \"someversion\",\n\t\t\t}\n\t\t\tforbidden = []string{\n\t\t\t\t\"help\", \"group\", \"kind\", \"version\",\n\t\t\t}\n\n\t\t\tfs = pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\n\t\t\tflagsToAppend := getFlags()\n\n\t\t\tflags = make([]external.Flag, len(flagsToAppend))\n\t\t\tcopy(flags, flagsToAppend)\n\n\t\t\targFilters = []argFilterFunc{\n\t\t\t\tgvkArgFilter, helpArgFilter,\n\t\t\t}\n\t\t\texternalFlagFilters = []externalFlagFilterFunc{\n\t\t\t\tgvkFlagFilter, helpFlagFilter,\n\t\t\t}\n\t\t})\n\n\t\tIt(\"isBooleanFlag should return true if boolean flag provided at index\", func() {\n\t\t\tExpect(isBooleanFlag(2, args)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"isBooleanFlag should return false if boolean flag not provided at index\", func() {\n\t\t\tExpect(isBooleanFlag(0, args)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"bindAllFlags should bind all flags\", func() {\n\t\t\tusage := \"Kubebuilder could not validate this flag with the external plugin. \" +\n\t\t\t\t\"Consult the external plugin documentation for more information.\"\n\n\t\t\tbindAllFlags(fs, filterArgs(args, argFilters))\n\t\t\tExpect(fs.HasFlags()).To(BeTrue())\n\t\t\tExpect(fs.Lookup(\"domain\")).NotTo(BeNil())\n\t\t\tExpect(fs.Lookup(\"domain\").Value.Type()).To(Equal(\"string\"))\n\t\t\tExpect(fs.Lookup(\"domain\").Usage).To(Equal(usage))\n\t\t\tExpect(fs.Lookup(\"boolean\")).NotTo(BeNil())\n\t\t\tExpect(fs.Lookup(\"boolean\").Value.Type()).To(Equal(\"bool\"))\n\t\t\tExpect(fs.Lookup(\"boolean\").Usage).To(Equal(usage))\n\t\t\tExpect(fs.Lookup(\"another\")).NotTo(BeNil())\n\t\t\tExpect(fs.Lookup(\"another\").Value.Type()).To(Equal(\"string\"))\n\t\t\tExpect(fs.Lookup(\"another\").Usage).To(Equal(usage))\n\n\t\t\tBy(\"bindAllFlags not have bound any forbidden flag after filtering\")\n\t\t\tfor i := range forbidden {\n\t\t\t\tExpect(fs.Lookup(forbidden[i])).To(BeNil())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"bindSpecificFlags should bind all flags in given []Flag\", func() {\n\t\t\tfilteredFlags := filterFlags(flags, externalFlagFilters)\n\t\t\tbindSpecificFlags(fs, filteredFlags)\n\n\t\t\tExpect(fs.HasFlags()).To(BeTrue())\n\n\t\t\tfor _, flag := range filteredFlags {\n\t\t\t\tExpect(fs.Lookup(flag.Name)).NotTo(BeNil())\n\t\t\t\t// we parse floats as float64 Go type so this check will account for that\n\t\t\t\tif flag.Type != floatVal {\n\t\t\t\t\tExpect(fs.Lookup(flag.Name).Value.Type()).To(Equal(flag.Type))\n\t\t\t\t} else {\n\t\t\t\t\tExpect(fs.Lookup(flag.Name).Value.Type()).To(Equal(\"float64\"))\n\t\t\t\t}\n\t\t\t\tExpect(fs.Lookup(flag.Name).Usage).To(Equal(flag.Usage))\n\t\t\t\tExpect(fs.Lookup(flag.Name).DefValue).To(Equal(flag.Default))\n\t\t\t}\n\n\t\t\tBy(\"bindSpecificFlags not have bound any forbidden flag after filtering\")\n\t\t\tfor i := range forbidden {\n\t\t\t\tExpect(fs.Lookup(forbidden[i])).To(BeNil())\n\t\t\t}\n\t\t})\n\t})\n\n\t// TODO(everettraven): Add tests for an external plugin setting the Metadata and Examples\n\tContext(\"Successfully retrieving metadata and examples from external plugin\", func() {\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\tmetadata       *plugin.SubcommandMetadata\n\t\t\tcheckMetadata  func()\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockValidMEOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\n\t\t\tpluginFileName = externalPlugin\n\t\t\tmetadata = &plugin.SubcommandMetadata{}\n\n\t\t\tcheckMetadata = func() {\n\t\t\t\tExpect(metadata.Description).Should(Equal(getMetadata().Description))\n\t\t\t\tExpect(metadata.Examples).Should(Equal(getMetadata().Examples))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should use the external plugin's metadata and examples for `init` subcommand\", func() {\n\t\t\tsc := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the external plugin's metadata and examples for `create api` subcommand\", func() {\n\t\t\tsc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the external plugin's metadata and examples for `create webhook` subcommand\", func() {\n\t\t\tsc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the external plugin's metadata and examples for `edit` subcommand\", func() {\n\t\t\tsc := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\t})\n\n\tContext(\"Failing to retrieve metadata and examples from external plugin\", func() {\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\tmetadata       *plugin.SubcommandMetadata\n\t\t\tcheckMetadata  func()\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\toutputGetter = &mockInValidOutputGetter{}\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\n\t\t\tpluginFileName = externalPlugin\n\t\t\tmetadata = &plugin.SubcommandMetadata{}\n\n\t\t\tcheckMetadata = func() {\n\t\t\t\tExpect(metadata.Description).Should(Equal(fmt.Sprintf(defaultMetadataTemplate, \"myexternalplugin\")))\n\t\t\t\tExpect(metadata.Examples).Should(BeEmpty())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should use the default metadata and examples for `init` subcommand\", func() {\n\t\t\tsc := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the default metadata and examples for `create api` subcommand\", func() {\n\t\t\tsc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the default metadata and examples for `create webhook` subcommand\", func() {\n\t\t\tsc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\n\t\tIt(\"should use the default metadata and examples for `edit` subcommand\", func() {\n\t\t\tsc := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: nil,\n\t\t\t}\n\n\t\t\tsc.UpdateMetadata(plugin.CLIMetadata{}, metadata)\n\n\t\t\tcheckMetadata()\n\t\t})\n\t})\n\n\tContext(\"Helper functions for Sending request to external plugin and parsing response\", func() {\n\t\tIt(\"getUniverseMap should return path to content mapping of all files in Filesystem\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tfiles := []struct {\n\t\t\t\tpath    string\n\t\t\t\tname    string\n\t\t\t\tcontent string\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tpath:    \"./\",\n\t\t\t\t\tname:    \"file\",\n\t\t\t\t\tcontent: \"level 0 file\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tpath:    \"dir/\",\n\t\t\t\t\tname:    \"file\",\n\t\t\t\t\tcontent: \"level 1 file\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tpath:    \"dir/subdir\",\n\t\t\t\t\tname:    \"file\",\n\t\t\t\t\tcontent: \"level 2 file\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// create files in Filesystem\n\t\t\tfor _, file := range files {\n\t\t\t\terr := fs.FS.MkdirAll(file.path, 0o700)\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\tf, err := fs.FS.Create(filepath.Join(file.path, file.name))\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\t_, err = f.Write([]byte(file.content))\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\t\terr = f.Close()\n\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t}\n\n\t\t\tuniverse, err := getUniverseMap(fs)\n\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(universe).To(HaveLen(len(files)))\n\n\t\t\tfor _, file := range files {\n\t\t\t\tcontent := universe[filepath.Join(file.path, file.name)]\n\t\t\t\tExpect(content).To(Equal(file.content))\n\t\t\t}\n\t\t})\n\t})\n\n\tContext(\"plugin chain propagation\", func() {\n\t\tfor _, tc := range pluginChainTestCases {\n\t\t\tcaseData := tc\n\t\t\tContext(caseData.name, func() {\n\t\t\t\tIt(\"keeps the CLI-provided chain when config omits pluginChain\", func() {\n\t\t\t\t\tsub := caseData.new()\n\n\t\t\t\t\tcliChain := []string{\"cli.plugin/v1\"}\n\t\t\t\t\tsub.SetPluginChain(cliChain)\n\n\t\t\t\t\tcliChain[0] = \"mutated\"\n\t\t\t\t\tExpect(caseData.get(sub)).To(Equal([]string{\"cli.plugin/v1\"}))\n\n\t\t\t\t\tcfg := v3.New()\n\t\t\t\t\tExpect(sub.InjectConfig(cfg)).To(Succeed())\n\t\t\t\t\tExpect(caseData.get(sub)).To(Equal([]string{\"cli.plugin/v1\"}))\n\n\t\t\t\t\tsub.SetPluginChain(nil)\n\t\t\t\t\tExpect(caseData.get(sub)).To(BeNil())\n\t\t\t\t})\n\n\t\t\t\tIt(\"prefers the config plugin chain when present\", func() {\n\t\t\t\t\tsub := caseData.new()\n\t\t\t\t\tsub.SetPluginChain([]string{\"cli.plugin/v1\"})\n\n\t\t\t\t\tcfg := v3.New()\n\t\t\t\t\texpected := []string{\"config.plugin/v2\"}\n\t\t\t\t\tExpect(cfg.SetPluginChain(expected)).To(Succeed())\n\n\t\t\t\t\tExpect(sub.InjectConfig(cfg)).To(Succeed())\n\t\t\t\t\tExpect(caseData.get(sub)).To(Equal(expected))\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t})\n\n\tContext(\"PluginChain is passed to external plugin\", func() {\n\t\tvar (\n\t\t\tpluginChainCaptured []string\n\t\t\tmockOutputGetter    *mockPluginChainCaptureGetter\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tmockOutputGetter = &mockPluginChainCaptureGetter{\n\t\t\t\tcapturedChain: &pluginChainCaptured,\n\t\t\t}\n\t\t\toutputGetter = mockOutputGetter\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\t\t})\n\n\t\tIt(\"should pass plugin chain to init subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\ti := initSubcommand{\n\t\t\t\tPath:        \"test.sh\",\n\t\t\t\tArgs:        []string{\"--domain\", \"example.com\"},\n\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"},\n\t\t\t}\n\n\t\t\terr := i.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to create api subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tc := createAPISubcommand{\n\t\t\t\tPath:        \"test.sh\",\n\t\t\t\tArgs:        []string{\"--group\", \"apps\", \"--version\", \"v1\", \"--kind\", \"MyKind\"},\n\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\t}\n\n\t\t\terr := c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to create webhook subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tw := createWebhookSubcommand{\n\t\t\t\tPath:        \"test.sh\",\n\t\t\t\tArgs:        []string{\"--group\", \"apps\", \"--version\", \"v1\", \"--kind\", \"MyKind\"},\n\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v3\"},\n\t\t\t}\n\n\t\t\terr := w.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v3\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to edit subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\te := editSubcommand{\n\t\t\t\tPath:        \"test.sh\",\n\t\t\t\tArgs:        []string{\"--multigroup\"},\n\t\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"declarative.go.kubebuilder.io/v1\"},\n\t\t\t}\n\n\t\t\terr := e.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\", \"declarative.go.kubebuilder.io/v1\"}))\n\t\t})\n\t})\n\n\tContext(\"with config injection\", func() {\n\t\tconst filePerm os.FileMode = 755\n\t\tvar (\n\t\t\tpluginFileName string\n\t\t\targs           []string\n\t\t\tf              afero.File\n\t\t\tfs             machinery.Filesystem\n\t\t\tmockGetter     *mockConfigOutputGetter\n\t\t\tcfg            *v3.Cfg\n\t\t\texpectedChain  []string\n\n\t\t\terr error\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tmockGetter = &mockConfigOutputGetter{}\n\t\t\toutputGetter = mockGetter\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\t\t\tfs = machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tpluginFileName = \"externalPlugin.sh\"\n\t\t\tpluginFilePath := filepath.Join(\"tmp\", \"externalPlugin\", pluginFileName)\n\n\t\t\terr = fs.FS.MkdirAll(filepath.Dir(pluginFilePath), filePerm)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tf, err = fs.FS.Create(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(f).ToNot(BeNil())\n\n\t\t\t_, err = fs.FS.Stat(pluginFilePath)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\targs = []string{\"--domain\", \"example.com\"}\n\n\t\t\tcfg = &v3.Cfg{\n\t\t\t\tVersion:    v3.Version,\n\t\t\t\tDomain:     \"test.domain\",\n\t\t\t\tRepository: \"github.com/test/repo\",\n\t\t\t\tName:       \"test-project\",\n\t\t\t}\n\n\t\t\texpectedChain = []string{\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"}\n\t\t\tExpect(cfg.SetPluginChain(expectedChain)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should pass config to external plugin on init subcommand\", func() {\n\t\t\ti := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tExpect(i.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr = i.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tExpect(mockGetter.capturedRequest).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"domain\"]).To(Equal(\"test.domain\"))\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"repo\"]).To(Equal(\"github.com/test/repo\"))\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"projectName\"]).To(Equal(\"test-project\"))\n\t\t\tExpect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain))\n\t\t})\n\n\t\tIt(\"should pass config to external plugin on create api subcommand\", func() {\n\t\t\tc := createAPISubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tExpect(c.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tExpect(mockGetter.capturedRequest).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"domain\"]).To(Equal(\"test.domain\"))\n\t\t\tExpect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain))\n\t\t})\n\n\t\tIt(\"should pass config to external plugin on create webhook subcommand\", func() {\n\t\t\tc := createWebhookSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tExpect(c.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr = c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tExpect(mockGetter.capturedRequest).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"domain\"]).To(Equal(\"test.domain\"))\n\t\t\tExpect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain))\n\t\t})\n\n\t\tIt(\"should pass config to external plugin on edit subcommand\", func() {\n\t\t\te := editSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tExpect(e.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr = e.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tExpect(mockGetter.capturedRequest).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config[\"domain\"]).To(Equal(\"test.domain\"))\n\t\t\tExpect(mockGetter.capturedRequest.PluginChain).To(Equal(expectedChain))\n\t\t})\n\n\t\tIt(\"should handle nil config gracefully\", func() {\n\t\t\ti := initSubcommand{\n\t\t\t\tPath: pluginFileName,\n\t\t\t\tArgs: args,\n\t\t\t}\n\n\t\t\tExpect(i.InjectConfig(nil)).To(Succeed())\n\n\t\t\terr = i.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tExpect(mockGetter.capturedRequest).ToNot(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.Config).To(BeNil())\n\t\t\tExpect(mockGetter.capturedRequest.PluginChain).To(BeNil())\n\t\t})\n\t})\n\n\tContext(\"PluginChain is passed to external plugin\", func() {\n\t\tvar (\n\t\t\tpluginChainCaptured []string\n\t\t\tmockOutputGetter    *mockPluginChainCaptureGetter\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tpluginChainCaptured = nil\n\t\t\tmockOutputGetter = &mockPluginChainCaptureGetter{\n\t\t\t\tcapturedChain: &pluginChainCaptured,\n\t\t\t}\n\t\t\toutputGetter = mockOutputGetter\n\t\t\tcurrentDirGetter = &mockValidOsWdGetter{}\n\t\t})\n\n\t\tIt(\"should pass plugin chain to init subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tcfg := &v3.Cfg{Version: v3.Version}\n\t\t\tExpect(cfg.SetPluginChain([]string{\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"})).To(Succeed())\n\n\t\t\ti := initSubcommand{\n\t\t\t\tPath: \"test.sh\",\n\t\t\t\tArgs: []string{\"--domain\", \"example.com\"},\n\t\t\t}\n\n\t\t\tExpect(i.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr := i.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\", \"kustomize.common.kubebuilder.io/v2\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to create api subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tcfg := &v3.Cfg{Version: v3.Version}\n\t\t\tExpect(cfg.SetPluginChain([]string{\"go.kubebuilder.io/v4\"})).To(Succeed())\n\n\t\t\tc := createAPISubcommand{\n\t\t\t\tPath: \"test.sh\",\n\t\t\t\tArgs: []string{\"--group\", \"apps\", \"--version\", \"v1\", \"--kind\", \"MyKind\"},\n\t\t\t}\n\n\t\t\tExpect(c.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr := c.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to create webhook subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tcfg := &v3.Cfg{Version: v3.Version}\n\t\t\tExpect(cfg.SetPluginChain([]string{\"go.kubebuilder.io/v3\"})).To(Succeed())\n\n\t\t\tw := createWebhookSubcommand{\n\t\t\t\tPath: \"test.sh\",\n\t\t\t\tArgs: []string{\"--group\", \"apps\", \"--version\", \"v1\", \"--kind\", \"MyKind\"},\n\t\t\t}\n\n\t\t\tExpect(w.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr := w.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v3\"}))\n\t\t})\n\n\t\tIt(\"should pass plugin chain to edit subcommand\", func() {\n\t\t\tfs := machinery.Filesystem{\n\t\t\t\tFS: afero.NewMemMapFs(),\n\t\t\t}\n\n\t\t\tcfg := &v3.Cfg{Version: v3.Version}\n\t\t\tExpect(cfg.SetPluginChain([]string{\"go.kubebuilder.io/v4\", \"declarative.go.kubebuilder.io/v1\"})).To(Succeed())\n\n\t\t\te := editSubcommand{\n\t\t\t\tPath: \"test.sh\",\n\t\t\t\tArgs: []string{\"--multigroup\"},\n\t\t\t}\n\n\t\t\tExpect(e.InjectConfig(cfg)).To(Succeed())\n\n\t\t\terr := e.Scaffold(fs)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\tExpect(pluginChainCaptured).To(Equal([]string{\"go.kubebuilder.io/v4\", \"declarative.go.kubebuilder.io/v1\"}))\n\t\t})\n\t})\n})\n\nfunc getFlags() []external.Flag {\n\treturn []external.Flag{\n\t\t{\n\t\t\tName:    \"captain\",\n\t\t\tType:    \"string\",\n\t\t\tUsage:   \"specify the ship captain\",\n\t\t\tDefault: \"jack-sparrow\",\n\t\t},\n\t\t{\n\t\t\tName:    \"sail\",\n\t\t\tType:    \"bool\",\n\t\t\tUsage:   \"deploy the sail\",\n\t\t\tDefault: \"false\",\n\t\t},\n\t\t{\n\t\t\tName:    \"crew-count\",\n\t\t\tType:    \"int\",\n\t\t\tUsage:   \"number of crew members\",\n\t\t\tDefault: \"123\",\n\t\t},\n\t\t{\n\t\t\tName:    \"treasure-value\",\n\t\t\tType:    \"float\",\n\t\t\tUsage:   \"value of treasure on board the ship\",\n\t\t\tDefault: \"123.45\",\n\t\t},\n\t}\n}\n\nfunc getMetadata() plugin.SubcommandMetadata {\n\treturn plugin.SubcommandMetadata{\n\t\tDescription: \"Test description\",\n\t\tExamples:    \"Test examples\",\n\t}\n}\n\nvar _ = Describe(\"Plugin\", func() {\n\tvar p Plugin\n\n\tBeforeEach(func() {\n\t\tp = Plugin{\n\t\t\tPName:    \"testplugin\",\n\t\t\tPVersion: plugin.Version{Number: 1},\n\t\t\tPSupportedProjectVersions: []config.Version{\n\t\t\t\t{Number: 3},\n\t\t\t},\n\t\t\tPath: \"/path/to/plugin\",\n\t\t\tArgs: []string{\"--flag\", \"value\"},\n\t\t}\n\t})\n\n\tIt(\"should return the plugin name\", func() {\n\t\tExpect(p.Name()).To(Equal(\"testplugin\"))\n\t})\n\n\tIt(\"should return the plugin version\", func() {\n\t\tExpect(p.Version()).To(Equal(plugin.Version{Number: 1}))\n\t})\n\n\tIt(\"should return supported project versions\", func() {\n\t\tExpect(p.SupportedProjectVersions()).To(Equal([]config.Version{{Number: 3}}))\n\t})\n\n\tIt(\"should return init subcommand\", func() {\n\t\tsub := p.GetInitSubcommand()\n\t\tExpect(sub).NotTo(BeNil())\n\t\tinitSub, ok := sub.(*initSubcommand)\n\t\tExpect(ok).To(BeTrue())\n\t\tExpect(initSub.Path).To(Equal(\"/path/to/plugin\"))\n\t\tExpect(initSub.Args).To(Equal([]string{\"--flag\", \"value\"}))\n\t})\n\n\tIt(\"should return create API subcommand\", func() {\n\t\tsub := p.GetCreateAPISubcommand()\n\t\tExpect(sub).NotTo(BeNil())\n\t\tapiSub, ok := sub.(*createAPISubcommand)\n\t\tExpect(ok).To(BeTrue())\n\t\tExpect(apiSub.Path).To(Equal(\"/path/to/plugin\"))\n\t\tExpect(apiSub.Args).To(Equal([]string{\"--flag\", \"value\"}))\n\t})\n\n\tIt(\"should return create webhook subcommand\", func() {\n\t\tsub := p.GetCreateWebhookSubcommand()\n\t\tExpect(sub).NotTo(BeNil())\n\t\twebhookSub, ok := sub.(*createWebhookSubcommand)\n\t\tExpect(ok).To(BeTrue())\n\t\tExpect(webhookSub.Path).To(Equal(\"/path/to/plugin\"))\n\t\tExpect(webhookSub.Args).To(Equal([]string{\"--flag\", \"value\"}))\n\t})\n\n\tIt(\"should return edit subcommand\", func() {\n\t\tsub := p.GetEditSubcommand()\n\t\tExpect(sub).NotTo(BeNil())\n\t\teditSub, ok := sub.(*editSubcommand)\n\t\tExpect(ok).To(BeTrue())\n\t\tExpect(editSub.Path).To(Equal(\"/path/to/plugin\"))\n\t\tExpect(editSub.Args).To(Equal([]string{\"--flag\", \"value\"}))\n\t})\n\n\tIt(\"should return empty deprecation warning\", func() {\n\t\tExpect(p.DeprecationWarning()).To(BeEmpty())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/external/helpers.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\tiofs \"io/fs\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/pflag\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar outputGetter ExecOutputGetter = &execOutputGetter{}\n\nconst defaultMetadataTemplate = `\n%s is an external plugin for scaffolding files to help with your Operator development.\n\nFor more information on how to use this external plugin, it is recommended to \nconsult the external plugin's documentation.\n`\n\n// ExecOutputGetter is an interface that implements the exec output method.\ntype ExecOutputGetter interface {\n\tGetExecOutput(req []byte, path string) ([]byte, error)\n}\n\ntype execOutputGetter struct{}\n\nfunc (e *execOutputGetter) GetExecOutput(request []byte, path string) ([]byte, error) {\n\tcmd := exec.Command(path) //nolint:gosec\n\tcmd.Stdin = bytes.NewBuffer(request)\n\tcmd.Stderr = os.Stderr\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting output for cmd %q: %w\", cmd, err)\n\t}\n\n\treturn out, nil\n}\n\nvar currentDirGetter OsWdGetter = &osWdGetter{}\n\n// OsWdGetter is an interface that implements the get current directory method.\ntype OsWdGetter interface {\n\tGetCurrentDir() (string, error)\n}\n\ntype osWdGetter struct{}\n\nfunc (o *osWdGetter) GetCurrentDir() (string, error) {\n\tcurrentDir, err := os.Getwd()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error getting current directory: %w\", err)\n\t}\n\n\treturn currentDir, nil\n}\n\nfunc makePluginRequest(req external.PluginRequest, path string) (*external.PluginResponse, error) {\n\treqBytes, err := json.Marshal(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshalling plugin request: %w\", err)\n\t}\n\n\tout, err := outputGetter.GetExecOutput(reqBytes, path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error executing plugin request: %w\", err)\n\t}\n\n\tres := external.PluginResponse{}\n\tif err = json.Unmarshal(out, &res); err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling plugin response: %w\", err)\n\t}\n\n\t// Error if the plugin failed.\n\tif res.Error {\n\t\treturn nil, fmt.Errorf(\"%s\", strings.Join(res.ErrorMsgs, \"\\n\"))\n\t}\n\n\treturn &res, nil\n}\n\n// getUniverseMap is a helper function that is used to read the current directory to build\n// the universe map.\n// It will return a map[string]string where the keys are relative paths to files in the directory\n// and values are the contents, or an error if an issue occurred while reading one of the files.\nfunc getUniverseMap(fs machinery.Filesystem) (map[string]string, error) {\n\tuniverse := map[string]string{}\n\n\terr := afero.Walk(fs.FS, \".\", func(path string, info iofs.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error walking path %q: %w\", path, err)\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tfile, err := fs.FS.Open(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error opening file %q: %w\", path, err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err = file.Close(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tcontent, err := io.ReadAll(file)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading file %q: %w\", path, err)\n\t\t}\n\n\t\tuniverse[path] = string(content)\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error walking the directory: %w\", err)\n\t}\n\n\treturn universe, nil\n}\n\nfunc handlePluginResponse(fs machinery.Filesystem, req external.PluginRequest, path string, cfg config.Config) error {\n\tvar err error\n\n\treq.Universe, err = getUniverseMap(fs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting universe map: %w\", err)\n\t}\n\n\t// Marshal config to include in the request if config is provided\n\tif cfg != nil {\n\t\tvar configData []byte\n\t\tconfigData, err = cfg.MarshalYAML()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error marshaling config: %w\", err)\n\t\t}\n\n\t\tvar configMap map[string]any\n\t\tif err = yaml.Unmarshal(configData, &configMap); err != nil {\n\t\t\treturn fmt.Errorf(\"error unmarshaling config to map: %w\", err)\n\t\t}\n\n\t\treq.Config = configMap\n\t}\n\n\tres, err := makePluginRequest(req, path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error making request to external plugin: %w\", err)\n\t}\n\n\tcurrentDir, err := currentDirGetter.GetCurrentDir()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting current directory: %w\", err)\n\t}\n\n\tfor filename, data := range res.Universe {\n\t\tfile := filepath.Join(currentDir, filename)\n\t\tdir := filepath.Dir(file)\n\n\t\t// create the directory if it does not exist\n\t\tif err = os.MkdirAll(dir, 0o750); err != nil {\n\t\t\treturn fmt.Errorf(\"error creating the directory: %w\", err)\n\t\t}\n\n\t\tf, createErr := fs.FS.Create(file)\n\t\tif createErr != nil {\n\t\t\treturn fmt.Errorf(\"error creating file %q: %w\", file, createErr)\n\t\t}\n\n\t\tdefer func() {\n\t\t\tif err = f.Close(); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\n\t\tif _, err = f.Write([]byte(data)); err != nil {\n\t\t\treturn fmt.Errorf(\"error writing file %q: %w\", file, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// getExternalPluginFlags is a helper function that is used to get a list of flags from an external plugin.\n// It will return []Flag if successful or an error if there is an issue attempting to get the list of flags.\nfunc getExternalPluginFlags(req external.PluginRequest, path string) ([]external.Flag, error) {\n\treq.Universe = map[string]string{}\n\n\tres, err := makePluginRequest(req, path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error making request to external plugin: %w\", err)\n\t}\n\n\treturn res.Flags, nil\n}\n\n// isBooleanFlag is a helper function to determine if an argument flag is a boolean flag\nfunc isBooleanFlag(argIndex int, args []string) bool {\n\treturn argIndex+1 < len(args) &&\n\t\tstrings.Contains(args[argIndex+1], \"--\") ||\n\t\targIndex+1 >= len(args)\n}\n\n// bindAllFlags will bind all flags passed into the subcommand by a user\nfunc bindAllFlags(fs *pflag.FlagSet, args []string) {\n\tdefaultFlagDescription := \"Kubebuilder could not validate this flag with the external plugin. \" +\n\t\t\"Consult the external plugin documentation for more information.\"\n\n\t// Bind all flags passed in\n\tfor i := range args {\n\t\tif strings.Contains(args[i], \"--\") {\n\t\t\tflag := strings.Replace(args[i], \"--\", \"\", 1)\n\t\t\t// Check if the flag is a boolean flag\n\t\t\tif isBooleanFlag(i, args) {\n\t\t\t\t_ = fs.Bool(flag, false, defaultFlagDescription)\n\t\t\t} else {\n\t\t\t\t_ = fs.String(flag, \"\", defaultFlagDescription)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// bindSpecificFlags binds flags that are specified by an external plugin as allowed.\nfunc bindSpecificFlags(fs *pflag.FlagSet, flags []external.Flag) {\n\t// Only bind flags returned by the external plugin\n\tfor _, flag := range flags {\n\t\tswitch flag.Type {\n\t\tcase \"bool\":\n\t\t\tdefaultValue, _ := strconv.ParseBool(flag.Default)\n\t\t\t_ = fs.Bool(flag.Name, defaultValue, flag.Usage)\n\t\tcase \"int\":\n\t\t\tdefaultValue, _ := strconv.Atoi(flag.Default)\n\t\t\t_ = fs.Int(flag.Name, defaultValue, flag.Usage)\n\t\tcase \"float\":\n\t\t\tdefaultValue, _ := strconv.ParseFloat(flag.Default, 64)\n\t\t\t_ = fs.Float64(flag.Name, defaultValue, flag.Usage)\n\t\tdefault:\n\t\t\t_ = fs.String(flag.Name, flag.Default, flag.Usage)\n\t\t}\n\t}\n}\n\nfunc filterFlags(flags []external.Flag, externalFlagFilters []externalFlagFilterFunc) []external.Flag {\n\tvar filteredFlags []external.Flag\n\tfor _, flag := range flags {\n\t\tok := true\n\t\tfor _, filter := range externalFlagFilters {\n\t\t\tif !filter(flag) {\n\t\t\t\tok = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok {\n\t\t\tfilteredFlags = append(filteredFlags, flag)\n\t\t}\n\t}\n\treturn filteredFlags\n}\n\nfunc filterArgs(args []string, argFilters []argFilterFunc) []string {\n\tvar filteredArgs []string\n\tfor _, arg := range args {\n\t\tok := true\n\t\tfor _, filter := range argFilters {\n\t\t\tif !filter(arg) {\n\t\t\t\tok = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok {\n\t\t\tfilteredArgs = append(filteredArgs, arg)\n\t\t}\n\t}\n\treturn filteredArgs\n}\n\ntype (\n\texternalFlagFilterFunc func(flag external.Flag) bool\n\targFilterFunc          func(arg string) bool\n)\n\nvar (\n\t// see gvkArgFilter\n\tgvkFlagFilter = func(flag external.Flag) bool {\n\t\treturn gvkArgFilter(flag.Name)\n\t}\n\t// gvkFlagFilter filters out any flag named \"group\", \"version\", \"kind\" as\n\t// they are already bound by kubebuilder\n\tgvkArgFilter = func(arg string) bool {\n\t\targ = strings.Replace(arg, \"--\", \"\", 1)\n\t\treturn !slices.Contains([]string{\n\t\t\t\"group\", \"version\", \"kind\",\n\t\t}, arg)\n\t}\n\n\t// see helpArgFilter\n\thelpFlagFilter = func(flag external.Flag) bool {\n\t\treturn helpArgFilter(flag.Name)\n\t}\n\t// helpArgFilter filters out any flag named \"help\" as its already bound\n\thelpArgFilter = func(arg string) bool {\n\t\targ = strings.Replace(arg, \"--\", \"\", 1)\n\t\treturn arg != \"help\"\n\t}\n)\n\nfunc bindExternalPluginFlags(fs *pflag.FlagSet, subcommand string, path string, args []string) {\n\treq := external.PluginRequest{\n\t\tAPIVersion: defaultAPIVersion,\n\t\tCommand:    \"flags\",\n\t\tArgs:       []string{\"--\" + subcommand},\n\t}\n\n\t// Get a list of flags for the init subcommand of the external plugin\n\t// If it returns an error, parse all flags passed by the user and let\n\t// the external plugin return an unknown flag error.\n\tflags, err := getExternalPluginFlags(req, path)\n\n\t// Filter Flags based on a set of filters that we do not want.\n\t// can be used to filter out non-overridable flags or other\n\t// criteria by creating your own filterFlagFunc\n\tif err != nil {\n\t\tbindAllFlags(fs, filterArgs(args, []argFilterFunc{\n\t\t\tgvkArgFilter,\n\t\t\thelpArgFilter,\n\t\t}))\n\t} else {\n\t\tbindSpecificFlags(fs, filterFlags(flags, []externalFlagFilterFunc{\n\t\t\tgvkFlagFilter,\n\t\t\thelpFlagFilter,\n\t\t}))\n\t}\n}\n\n// setExternalPluginMetadata is a helper function that sets the subcommand\n// metadata that is used when the help text is shown for a subcommand.\n// It will attempt to get the Metadata from the external plugin. If the\n// external plugin returns no Metadata or an error, a default will be used.\nfunc setExternalPluginMetadata(subcommand, path string, subcmdMeta *plugin.SubcommandMetadata) {\n\tfileName := filepath.Base(path)\n\tsubcmdMeta.Description = fmt.Sprintf(defaultMetadataTemplate, fileName[:len(fileName)-len(filepath.Ext(fileName))])\n\n\tres, _ := getExternalPluginMetadata(subcommand, path)\n\n\tif res != nil {\n\t\tif res.Description != \"\" {\n\t\t\tsubcmdMeta.Description = res.Description\n\t\t}\n\n\t\tif res.Examples != \"\" {\n\t\t\tsubcmdMeta.Examples = res.Examples\n\t\t}\n\t}\n}\n\n// fetchExternalPluginMetadata performs the actual request to the\n// external plugin to get the metadata. It returns the metadata\n// or an error if an error occurs during the fetch process.\nfunc getExternalPluginMetadata(subcommand, path string) (*plugin.SubcommandMetadata, error) {\n\treq := external.PluginRequest{\n\t\tAPIVersion: defaultAPIVersion,\n\t\tCommand:    \"metadata\",\n\t\tArgs:       []string{\"--\" + subcommand},\n\t\tUniverse:   map[string]string{},\n\t}\n\n\tres, err := makePluginRequest(req, path)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error making request to external plugin: %w\", err)\n\t}\n\n\treturn &res.Metadata, nil\n}\n"
  },
  {
    "path": "pkg/plugins/external/helpers_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar _ = Describe(\"helpers\", func() {\n\tContext(\"isBooleanFlag\", func() {\n\t\tIt(\"should return true when next arg starts with --\", func() {\n\t\t\targs := []string{\"--flag1\", \"--flag2\", \"value\"}\n\t\t\tExpect(isBooleanFlag(0, args)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return true when at end of args\", func() {\n\t\t\targs := []string{\"--flag1\", \"--flag2\"}\n\t\t\tExpect(isBooleanFlag(1, args)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return false when next arg is a value\", func() {\n\t\t\targs := []string{\"--flag1\", \"value\", \"--flag2\"}\n\t\t\tExpect(isBooleanFlag(0, args)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return true for last argument\", func() {\n\t\t\targs := []string{\"--flag\"}\n\t\t\tExpect(isBooleanFlag(0, args)).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"filterFlags\", func() {\n\t\tvar testFlags []external.Flag\n\n\t\tBeforeEach(func() {\n\t\t\ttestFlags = []external.Flag{\n\t\t\t\t{Name: \"group\", Type: \"string\"},\n\t\t\t\t{Name: \"version\", Type: \"string\"},\n\t\t\t\t{Name: \"kind\", Type: \"string\"},\n\t\t\t\t{Name: \"custom\", Type: \"string\"},\n\t\t\t\t{Name: \"help\", Type: \"bool\"},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should filter flags based on single filter\", func() {\n\t\t\tfilter := func(flag external.Flag) bool {\n\t\t\t\treturn flag.Name != \"group\"\n\t\t\t}\n\t\t\tresult := filterFlags(testFlags, []externalFlagFilterFunc{filter})\n\n\t\t\tExpect(result).To(HaveLen(4))\n\t\t\tfor _, flag := range result {\n\t\t\t\tExpect(flag.Name).NotTo(Equal(\"group\"))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should filter flags based on multiple filters\", func() {\n\t\t\tfilter1 := func(flag external.Flag) bool {\n\t\t\t\treturn flag.Name != \"group\"\n\t\t\t}\n\t\t\tfilter2 := func(flag external.Flag) bool {\n\t\t\t\treturn flag.Name != \"help\"\n\t\t\t}\n\t\t\tresult := filterFlags(testFlags, []externalFlagFilterFunc{filter1, filter2})\n\n\t\t\tExpect(result).To(HaveLen(3))\n\t\t\tExpect(result).To(ContainElement(external.Flag{Name: \"version\", Type: \"string\"}))\n\t\t\tExpect(result).To(ContainElement(external.Flag{Name: \"kind\", Type: \"string\"}))\n\t\t\tExpect(result).To(ContainElement(external.Flag{Name: \"custom\", Type: \"string\"}))\n\t\t})\n\n\t\tIt(\"should return empty when all flags filtered out\", func() {\n\t\t\tfilter := func(_ external.Flag) bool {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tresult := filterFlags(testFlags, []externalFlagFilterFunc{filter})\n\t\t\tExpect(result).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should return all flags when no filters reject\", func() {\n\t\t\tfilter := func(_ external.Flag) bool {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tresult := filterFlags(testFlags, []externalFlagFilterFunc{filter})\n\t\t\tExpect(result).To(Equal(testFlags))\n\t\t})\n\t})\n\n\tContext(\"filterArgs\", func() {\n\t\tIt(\"should filter args based on single filter\", func() {\n\t\t\targs := []string{\"--group\", \"--version\", \"--custom\", \"--help\"}\n\t\t\tfilter := func(arg string) bool {\n\t\t\t\treturn arg != \"--group\"\n\t\t\t}\n\t\t\tresult := filterArgs(args, []argFilterFunc{filter})\n\n\t\t\tExpect(result).To(Equal([]string{\"--version\", \"--custom\", \"--help\"}))\n\t\t})\n\n\t\tIt(\"should filter args based on multiple filters\", func() {\n\t\t\targs := []string{\"--group\", \"--version\", \"--custom\"}\n\t\t\tfilter1 := func(arg string) bool {\n\t\t\t\treturn arg != \"--group\"\n\t\t\t}\n\t\t\tfilter2 := func(arg string) bool {\n\t\t\t\treturn arg != \"--version\"\n\t\t\t}\n\t\t\tresult := filterArgs(args, []argFilterFunc{filter1, filter2})\n\n\t\t\tExpect(result).To(Equal([]string{\"--custom\"}))\n\t\t})\n\n\t\tIt(\"should return empty when all args filtered out\", func() {\n\t\t\targs := []string{\"--arg1\", \"--arg2\"}\n\t\t\tfilter := func(_ string) bool {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tresult := filterArgs(args, []argFilterFunc{filter})\n\t\t\tExpect(result).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should return all args when no filters reject\", func() {\n\t\t\targs := []string{\"--arg1\", \"--arg2\"}\n\t\t\tfilter := func(_ string) bool {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tresult := filterArgs(args, []argFilterFunc{filter})\n\t\t\tExpect(result).To(Equal(args))\n\t\t})\n\t})\n\n\tContext(\"gvkArgFilter\", func() {\n\t\tIt(\"should filter out group flag\", func() {\n\t\t\tExpect(gvkArgFilter(\"group\")).To(BeFalse())\n\t\t\tExpect(gvkArgFilter(\"--group\")).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should filter out version flag\", func() {\n\t\t\tExpect(gvkArgFilter(\"version\")).To(BeFalse())\n\t\t\tExpect(gvkArgFilter(\"--version\")).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should filter out kind flag\", func() {\n\t\t\tExpect(gvkArgFilter(\"kind\")).To(BeFalse())\n\t\t\tExpect(gvkArgFilter(\"--kind\")).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should allow other flags\", func() {\n\t\t\tExpect(gvkArgFilter(\"custom\")).To(BeTrue())\n\t\t\tExpect(gvkArgFilter(\"--custom\")).To(BeTrue())\n\t\t\tExpect(gvkArgFilter(\"domain\")).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"gvkFlagFilter\", func() {\n\t\tIt(\"should filter out group, version, kind flags\", func() {\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"group\"})).To(BeFalse())\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"version\"})).To(BeFalse())\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"kind\"})).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should allow other flags\", func() {\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"custom\"})).To(BeTrue())\n\t\t\tExpect(gvkFlagFilter(external.Flag{Name: \"domain\"})).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"helpArgFilter\", func() {\n\t\tIt(\"should filter out help flag\", func() {\n\t\t\tExpect(helpArgFilter(\"help\")).To(BeFalse())\n\t\t\tExpect(helpArgFilter(\"--help\")).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should allow other flags\", func() {\n\t\t\tExpect(helpArgFilter(\"custom\")).To(BeTrue())\n\t\t\tExpect(helpArgFilter(\"--custom\")).To(BeTrue())\n\t\t\tExpect(helpArgFilter(\"helpful\")).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"helpFlagFilter\", func() {\n\t\tIt(\"should filter out help flag\", func() {\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"help\"})).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should allow other flags\", func() {\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"custom\"})).To(BeTrue())\n\t\t\tExpect(helpFlagFilter(external.Flag{Name: \"helpful\"})).To(BeTrue())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/external/init.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage external\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar _ plugin.InitSubcommand = &initSubcommand{}\n\ntype initSubcommand struct {\n\tPath        string\n\tArgs        []string\n\tpluginChain []string\n\tconfig      config.Config\n}\n\n// InjectConfig injects the project configuration to access plugin chain information\n\nfunc (p *initSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tif chain := c.GetPluginChain(); len(chain) > 0 {\n\t\tp.pluginChain = append([]string(nil), chain...)\n\t}\n\n\treturn nil\n}\n\nfunc (p *initSubcommand) SetPluginChain(chain []string) {\n\tif len(chain) == 0 {\n\t\tp.pluginChain = nil\n\t\treturn\n\t}\n\n\tp.pluginChain = append([]string(nil), chain...)\n}\n\nfunc (p *initSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsetExternalPluginMetadata(\"init\", p.Path, subcmdMeta)\n}\n\nfunc (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tbindExternalPluginFlags(fs, \"init\", p.Path, p.Args)\n}\n\nfunc (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {\n\treq := external.PluginRequest{\n\t\tAPIVersion:  defaultAPIVersion,\n\t\tCommand:     \"init\",\n\t\tArgs:        p.Args,\n\t\tPluginChain: p.pluginChain,\n\t}\n\n\terr := handlePluginResponse(fs, req, p.Path, p.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/external/plugin.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar _ plugin.Full = Plugin{}\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\tPName                     string\n\tPVersion                  plugin.Version\n\tPSupportedProjectVersions []config.Version\n\n\tPath string\n\tArgs []string\n}\n\n// Name returns the name of the plugin\nfunc (p Plugin) Name() string { return p.PName }\n\n// Version returns the version of the plugin\nfunc (p Plugin) Version() plugin.Version { return p.PVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (p Plugin) SupportedProjectVersions() []config.Version { return p.PSupportedProjectVersions }\n\n// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding\nfunc (p Plugin) GetInitSubcommand() plugin.InitSubcommand {\n\treturn &initSubcommand{\n\t\tPath: p.Path,\n\t\tArgs: p.Args,\n\t}\n}\n\n// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis\nfunc (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand {\n\treturn &createAPISubcommand{\n\t\tPath: p.Path,\n\t\tArgs: p.Args,\n\t}\n}\n\n// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks\nfunc (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {\n\treturn &createWebhookSubcommand{\n\t\tPath: p.Path,\n\t\tArgs: p.Args,\n\t}\n}\n\n// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand {\n\treturn &editSubcommand{\n\t\tPath: p.Path,\n\t\tArgs: p.Args,\n\t}\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/external/webhook.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage external\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/external\"\n)\n\nvar _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}\n\ntype createWebhookSubcommand struct {\n\tPath        string\n\tArgs        []string\n\tpluginChain []string\n\tconfig      config.Config\n}\n\n// InjectConfig injects the project configuration so external plugins can read the PROJECT file.\nfunc (p *createWebhookSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\tif c == nil {\n\t\treturn nil\n\t}\n\n\tif chain := c.GetPluginChain(); len(chain) > 0 {\n\t\tp.pluginChain = append([]string(nil), chain...)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createWebhookSubcommand) SetPluginChain(chain []string) {\n\tif len(chain) == 0 {\n\t\tp.pluginChain = nil\n\t\treturn\n\t}\n\n\tp.pluginChain = append([]string(nil), chain...)\n}\n\nfunc (p *createWebhookSubcommand) InjectResource(*resource.Resource) error {\n\t// Do nothing since resource flags are passed to the external plugin directly.\n\treturn nil\n}\n\nfunc (p *createWebhookSubcommand) UpdateMetadata(_ plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsetExternalPluginMetadata(\"webhook\", p.Path, subcmdMeta)\n}\n\nfunc (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tbindExternalPluginFlags(fs, \"webhook\", p.Path, p.Args)\n}\n\nfunc (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {\n\treq := external.PluginRequest{\n\t\tAPIVersion:  defaultAPIVersion,\n\t\tCommand:     \"create webhook\",\n\t\tArgs:        p.Args,\n\t\tPluginChain: p.pluginChain,\n\t}\n\n\terr := handlePluginResponse(fs, req, p.Path, p.config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds\"\n)\n\nvar _ plugin.CreateAPISubcommand = &createAPISubcommand{}\n\ntype createAPISubcommand struct {\n\tconfig config.Config\n\n\toptions *goPlugin.Options\n\n\tresource *resource.Resource\n\n\t// image indicates the image that will be used to scaffold the deployment\n\timage string\n\n\t// runMake indicates whether to run make or not after scaffolding APIs\n\trunMake bool\n\n\t// runManifests indicates whether to run manifests or not after scaffolding APIs\n\trunManifests bool\n\n\t// imageCommand indicates the command that we should use to init the deployment\n\timageContainerCommand string\n\n\t// imageContainerPort indicates the port that we should use in the scaffold\n\timageContainerPort string\n\n\t// runAsUser indicates the user-id used for running the container\n\trunAsUser string\n}\n\nfunc (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\t//nolint:lll\n\tsubcmdMeta.Description = `Scaffold the code implementation to deploy and manage your Operand which is represented by the API informed and will be reconciled by its controller. This plugin will generate the code implementation to help you out.\n\n\tNote: In general, it’s recommended to have one controller responsible for managing each API created for the project to properly follow the design goals set by Controller Runtime(https://github.com/kubernetes-sigs/controller-runtime).\n\n\tThis plugin will work as the common behaviour of the flag --force and will scaffold the API and controller always. Use core types or external APIs is not officially support by default with.\n`\n\t//nolint:lll\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Create a frigates API with Group: ship, Version: v1beta1, Kind: Frigate to represent the\n\tImage: example.com/frigate:v0.0.1 and its controller with a code to deploy and manage this Operand.\n\n\tNote that in the following example we are also adding the optional options to let you inform the command which should be used to create the container and initialize itvia the flag --image-container-command as the Port that should be used\n\n\t- By informing the command (--image-container-command=\"memcached,--memory-limit=64,-o,modern,-v\") your deployment will be scaffold with, i.e.:\n\n\t\tCommand: []string{\"memcached\",\"--memory-limit=64\",\"-o\",\"modern\",\"-v\"},\n\n\t- By informing the Port (--image-container-port) will deployment will be scaffold with, i.e:\n\n\t\tPorts: []corev1.ContainerPort{\n\t\t\tContainerPort: Memcached.Spec.ContainerPort,\n\t\t\tName:          \"Memcached\",\n\t\t},\n\n\tTherefore, the default values informed will be used to scaffold specs for the API.\n\n  %[1]s create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.6.15-alpine --image-container-command=\"memcached --memory-limit=64 modern -v\" --image-container-port=\"11211\" --plugins=\"%[2]s\" --make=false --namespaced=false\n\n  # Generate the manifests\n  make manifests\n\n  # Install CRDs into the Kubernetes cluster using kubectl apply\n  make install\n\n  # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config\n  make run\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.StringVar(&p.image, \"image\", \"\", \"inform the Operand image. \"+\n\t\t\"The controller will be scaffolded with an example code to deploy and manage this image.\")\n\n\tfs.StringVar(&p.imageContainerCommand, \"image-container-command\", \"\", \"[Optional] if informed, \"+\n\t\t\"will be used to scaffold the container command that should be used to init a container to run the image in \"+\n\t\t\"the controller and its spec in the API (CRD/CR). (i.e. \"+\n\t\t\"--image-container-command=\\\"memcached,--memory-limit=64,modern,-o,-v\\\")\")\n\tfs.StringVar(&p.imageContainerPort, \"image-container-port\", \"\", \"[Optional] if informed, \"+\n\t\t\"will be used to scaffold the container port that should be used by container image in \"+\n\t\t\"the controller and its spec in the API (CRD/CR). (i.e --image-container-port=\\\"11211\\\") \")\n\tfs.StringVar(&p.runAsUser, \"run-as-user\", \"\", \"User-Id for the container formed will be set to this value\")\n\n\tfs.BoolVar(&p.runMake, \"make\", true, \"if true, run `make generate` after generating files\")\n\tfs.BoolVar(&p.runManifests, \"manifests\", true, \"if true, run `make manifests` after generating files\")\n\n\tp.options = &goPlugin.Options{}\n\n\tfs.StringVar(&p.options.Plural, \"plural\", \"\", \"resource irregular plural form\")\n}\n\nfunc (p *createAPISubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) InjectResource(res *resource.Resource) error {\n\tp.resource = res\n\tp.options.DoAPI = true\n\tp.options.DoController = true\n\tp.options.Namespaced = true\n\n\tp.options.UpdateResource(p.resource, p.config)\n\n\tif err := p.resource.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"error validating resource: %w\", err)\n\t}\n\n\t// Check that the provided group can be added to the project\n\tif !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {\n\t\treturn fmt.Errorf(\"multiple groups are not allowed by default, \" +\n\t\t\t\"to enable multi-group visit https://kubebuilder.io/migration/multi-group.html\")\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {\n\tif len(p.image) == 0 {\n\t\treturn fmt.Errorf(\"you MUST inform the image that will be used in the reconciliation\")\n\t}\n\n\tisGoV3 := false\n\tfor _, pluginKey := range p.config.GetPluginChain() {\n\t\tif strings.Contains(pluginKey, \"go.kubebuilder.io/v3\") {\n\t\t\tisGoV3 = true\n\t\t}\n\t}\n\n\tdefaultMainPath := \"cmd/main.go\"\n\tif isGoV3 {\n\t\tdefaultMainPath = \"main.go\"\n\t}\n\t// check if main.go is present in the cmd/ directory\n\tif _, err := os.Stat(defaultMainPath); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"main.go file should be present in %s\", defaultMainPath)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {\n\tlog.Info(\"updating scaffold with deploy-image/v1alpha1 plugin...\")\n\n\tscaffolder := scaffolds.NewDeployImageScaffolder(p.config,\n\t\t*p.resource,\n\t\tp.image,\n\t\tp.imageContainerCommand,\n\t\tp.imageContainerPort,\n\t\tp.runAsUser)\n\tscaffolder.InjectFS(fs)\n\terr := scaffolder.Scaffold()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding deploy-image plugin: %w\", err)\n\t}\n\n\t// Save resource info to PROJECT file\n\tkey := plugin.GetPluginKeyForConfig(p.config.GetPluginChain(), Plugin{})\n\tcanonicalKey := plugin.KeyFor(Plugin{})\n\tcfg := PluginConfig{}\n\tif err = p.config.DecodePluginConfig(key, &cfg); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\t// Config version doesn't support plugin metadata\n\t\t\treturn nil\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tif key != canonicalKey {\n\t\t\t\tif decodeErr := p.config.DecodePluginConfig(canonicalKey, &cfg); decodeErr != nil {\n\t\t\t\t\tif errors.As(decodeErr, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(decodeErr, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", decodeErr)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err)\n\t\t}\n\t}\n\n\tconfigDataOptions := options{\n\t\tImage:            p.image,\n\t\tContainerCommand: p.imageContainerCommand,\n\t\tContainerPort:    p.imageContainerPort,\n\t\tRunAsUser:        p.runAsUser,\n\t}\n\tcfg.Resources = append(cfg.Resources, ResourceData{\n\t\tGroup:   p.resource.Group,\n\t\tDomain:  p.resource.Domain,\n\t\tVersion: p.resource.Version,\n\t\tKind:    p.resource.Kind,\n\t\tOptions: configDataOptions,\n\t})\n\n\tif err = p.config.EncodePluginConfig(key, cfg); err != nil {\n\t\treturn fmt.Errorf(\"error encoding plugin configuration: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) PostScaffold() error {\n\terr := util.RunCmd(\"Update dependencies\", \"go\", \"mod\", \"tidy\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating go dependencies: %w\", err)\n\t}\n\tif p.runMake && p.resource.HasAPI() {\n\t\terr = util.RunCmd(\"Running make\", \"make\", \"generate\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"ailed running make generate: %w\", err)\n\t\t}\n\t}\n\n\tif p.runManifests && p.resource.HasAPI() {\n\t\terr = util.RunCmd(\"Running make\", \"make\", \"manifests\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed running make manifests: %w\", err)\n\t\t}\n\t}\n\n\tfmt.Print(\"Next: check the implementation of your new API and controller. \" +\n\t\t\"If you do changes in the API run the manifests with:\\n$ make manifests\\n\")\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/api_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n)\n\nvar _ = Describe(\"createAPISubcommand\", func() {\n\tvar (\n\t\tsubCmd *createAPISubcommand\n\t\tcfg    config.Config\n\t\tres    *resource.Resource\n\t\tfs     machinery.Filesystem\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &createAPISubcommand{}\n\t\tcfg = cfgv3.New()\n\t\t_ = cfg.SetRepository(\"github.com/example/test\")\n\n\t\tsubCmd.options = &goPlugin.Options{}\n\t\tres = &resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"example.com\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1alpha1\",\n\t\t\t\tKind:    \"Memcached\",\n\t\t\t},\n\t\t\tPlural:   \"memcacheds\",\n\t\t\tAPI:      &resource.API{},\n\t\t\tWebhooks: &resource.Webhooks{},\n\t\t}\n\n\t\tfs = machinery.Filesystem{FS: afero.NewMemMapFs()}\n\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t})\n\n\tContext(\"PreScaffold validation\", func() {\n\t\tIt(\"should require image flag to be set\", func() {\n\t\t\tsubCmd.image = \"\"\n\n\t\t\terr := subCmd.PreScaffold(fs)\n\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"you MUST inform the image\"))\n\t\t})\n\n\t\tIt(\"should succeed when image is provided\", func() {\n\t\t\tsubCmd.image = \"memcached:1.6.15-alpine\"\n\n\t\t\ttmpDir, err := os.MkdirTemp(\"\", \"deploy-image-test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.RemoveAll(tmpDir) }()\n\n\t\t\toriginalDir, err := os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.Chdir(originalDir) }()\n\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.MkdirAll(\"cmd\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(filepath.Join(\"cmd\", \"main.go\"), []byte(\"package main\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = subCmd.PreScaffold(fs)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should check for cmd/main.go in go/v4 projects\", func() {\n\t\t\tsubCmd.image = \"busybox:1.36.1\"\n\t\t\t_ = cfg.SetPluginChain([]string{\"go.kubebuilder.io/v4\"})\n\n\t\t\ttmpDir, err := os.MkdirTemp(\"\", \"deploy-image-test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.RemoveAll(tmpDir) }()\n\n\t\t\toriginalDir, err := os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.Chdir(originalDir) }()\n\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = subCmd.PreScaffold(fs)\n\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"main.go file should be present in cmd/main.go\"))\n\t\t})\n\n\t\tIt(\"should check for main.go in go/v3 projects\", func() {\n\t\t\tsubCmd.image = \"busybox:1.36.1\"\n\t\t\t_ = cfg.SetPluginChain([]string{\"go.kubebuilder.io/v3\"})\n\n\t\t\ttmpDir, err := os.MkdirTemp(\"\", \"deploy-image-test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.RemoveAll(tmpDir) }()\n\n\t\t\toriginalDir, err := os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.Chdir(originalDir) }()\n\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = subCmd.PreScaffold(fs)\n\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"main.go file should be present in main.go\"))\n\t\t})\n\t})\n\n\tContext(\"InjectResource validation\", func() {\n\t\tIt(\"should set API and controller flags automatically\", func() {\n\t\t\terr := subCmd.InjectResource(res)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(subCmd.options.DoAPI).To(BeTrue())\n\t\t\tExpect(subCmd.options.DoController).To(BeTrue())\n\t\t\tExpect(subCmd.options.Namespaced).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should prevent multiple groups in single-group project\", func() {\n\t\t\tfirstRes := resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"ship\",\n\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Frigate\",\n\t\t\t\t},\n\t\t\t\tPlural: \"frigates\",\n\t\t\t\tAPI:    &resource.API{CRDVersion: \"v1\"},\n\t\t\t}\n\t\t\tExpect(cfg.AddResource(firstRes)).To(Succeed())\n\n\t\t\tres.Group = \"example.com\"\n\t\t\tres.Plural = \"memcacheds\"\n\n\t\t\terr := subCmd.InjectResource(res)\n\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"multiple groups are not allowed\"))\n\t\t})\n\n\t\tIt(\"should allow multiple groups when multigroup is enabled\", func() {\n\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\n\t\t\tfirstRes := resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"ship\",\n\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Frigate\",\n\t\t\t\t},\n\t\t\t\tPlural: \"frigates\",\n\t\t\t\tAPI:    &resource.API{CRDVersion: \"v1\"},\n\t\t\t}\n\t\t\tExpect(cfg.AddResource(firstRes)).To(Succeed())\n\n\t\t\tres.Group = \"example.com\"\n\n\t\t\tExpect(subCmd.InjectResource(res)).To(Succeed())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/plugin.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n)\n\nconst pluginName = \"deploy-image.\" + golang.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 1, Stage: stage.Alpha}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\nvar _ plugin.CreateAPI = Plugin{}\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\tcreateAPISubcommand\n}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis\nfunc (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }\n\n// PluginConfig defines the structure that will be used to track the data\ntype PluginConfig struct {\n\tResources []ResourceData `json:\"resources,omitempty\"`\n}\n\n// ResourceData store the resource data used in the plugin\ntype ResourceData struct {\n\tGroup   string  `json:\"group,omitempty\"`\n\tDomain  string  `json:\"domain,omitempty\"`\n\tVersion string  `json:\"version\"`\n\tKind    string  `json:\"kind\"`\n\tOptions options `json:\"options,omitempty\"`\n}\n\ntype options struct {\n\tImage            string `json:\"image,omitempty\"`\n\tContainerCommand string `json:\"containerCommand,omitempty\"`\n\tContainerPort    string `json:\"containerPort,omitempty\"`\n\tRunAsUser        string `json:\"runAsUser,omitempty\"`\n}\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Scaffolds a CRD+controller to deploy an image-based Operand\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/plugin_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"testing\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\n// TestGetPluginKeyForConfigIntegration tests that the plugin correctly resolves\n// its key based on the plugin chain, supporting custom bundle names.\nfunc TestGetPluginKeyForConfigIntegration(t *testing.T) {\n\tp := Plugin{}\n\n\ttests := []struct {\n\t\tname        string\n\t\tpluginChain []string\n\t\texpected    string\n\t\tdescription string\n\t}{\n\t\t{\n\t\t\tname:        \"exact match\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"deploy-image.go.kubebuilder.io/v1-alpha\"},\n\t\t\texpected:    \"deploy-image.go.kubebuilder.io/v1-alpha\",\n\t\t\tdescription: \"When plugin is used directly, it should use its own key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"bundle match with custom domain\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"deploy-image.custom-domain/v1-alpha\"},\n\t\t\texpected:    \"deploy-image.custom-domain/v1-alpha\",\n\t\t\tdescription: \"When plugin is wrapped in bundle with custom domain, it should use bundle's key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"bundle match with operator-sdk domain\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"deploy-image.operator-sdk.io/v1-alpha\"},\n\t\t\texpected:    \"deploy-image.operator-sdk.io/v1-alpha\",\n\t\t\tdescription: \"When plugin is wrapped in operator-sdk bundle, it should use bundle's key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no match - fallback to plugin key\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\"},\n\t\t\texpected:    \"deploy-image.go.kubebuilder.io/v1-alpha\",\n\t\t\tdescription: \"When no matching key in chain, fallback to plugin's own key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"version mismatch - fallback\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"deploy-image.custom-domain/v2-alpha\"},\n\t\t\texpected:    \"deploy-image.go.kubebuilder.io/v1-alpha\",\n\t\t\tdescription: \"When version doesn't match, fallback to plugin's own key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"base name mismatch - fallback\",\n\t\t\tpluginChain: []string{\"go.kubebuilder.io/v4\", \"other-plugin.custom-domain/v1-alpha\"},\n\t\t\texpected:    \"deploy-image.go.kubebuilder.io/v1-alpha\",\n\t\t\tdescription: \"When base name doesn't match, fallback to plugin's own key\",\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple bundles - choose first match\",\n\t\t\tpluginChain: []string{\"deploy-image.bundle-a.example.com/v1-alpha\", \"deploy-image.bundle-b.example.com/v1-alpha\"},\n\t\t\texpected:    \"deploy-image.bundle-a.example.com/v1-alpha\",\n\t\t\tdescription: \"When multiple bundle keys match, select the first occurrence\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := plugin.GetPluginKeyForConfig(tt.pluginChain, p)\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"%s: expected key %q, got %q\", tt.description, tt.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\tkustomizev2scaffolds \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers\"\n\tgolangv4scaffolds \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n)\n\nvar _ plugins.Scaffolder = &apiScaffolder{}\n\n// apiScaffolder contains configuration for generating scaffolding for Go type\n// representing the API and controller that implements the behavior for the API.\ntype apiScaffolder struct {\n\tconfig    config.Config\n\tresource  resource.Resource\n\timage     string\n\tcommand   string\n\tport      string\n\trunAsUser string\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewDeployImageScaffolder returns a new Scaffolder for declarative\nfunc NewDeployImageScaffolder(cfg config.Config, res resource.Resource, image,\n\tcommand, port, runAsUser string,\n) plugins.Scaffolder {\n\treturn &apiScaffolder{\n\t\tconfig:    cfg,\n\t\tresource:  res,\n\t\timage:     image,\n\t\tcommand:   command,\n\t\tport:      port,\n\t\trunAsUser: runAsUser,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) Scaffold() error {\n\tlog.Info(\"Writing scaffold for you to edit...\")\n\n\tif err := s.scaffoldCreateAPI(); err != nil {\n\t\treturn err\n\t}\n\n\t// Define the boilerplate file path\n\tboilerplatePath := filepath.Join(\"hack\", \"boilerplate.go.txt\")\n\n\t// Load the boilerplate\n\tboilerplate, err := afero.ReadFile(s.fs.FS, boilerplatePath)\n\tif err != nil {\n\t\tif errors.Is(err, afero.ErrFileNotFound) {\n\t\t\tlog.Warn(\"unable to find boilerplate file. \"+\n\t\t\t\t\"This file is used to generate the license header in the project.\\n\"+\n\t\t\t\t\"Note that controller-gen will also use this. Ensure that you \"+\n\t\t\t\t\"add the license file or configure your project accordingly\",\n\t\t\t\t\"file_path\", boilerplatePath, \"error\", err)\n\t\t\tboilerplate = []byte(\"\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"error scaffolding API/controller: failed to load boilerplate: %w\", err)\n\t\t}\n\t}\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t\tmachinery.WithBoilerplate(string(boilerplate)),\n\t\tmachinery.WithResource(&s.resource),\n\t)\n\n\tif err := scaffold.Execute(\n\t\t&api.Types{Port: s.port},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error updating APIs: %w\", err)\n\t}\n\n\tif err := scaffold.Execute(\n\t\t&samples.CRDSample{Port: s.port},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error updating config/samples: %w\", err)\n\t}\n\n\tcontroller := &controllers.Controller{\n\t\tControllerRuntimeVersion: golangv4scaffolds.ControllerRuntimeVersion,\n\t}\n\n\tif err := scaffold.Execute(\n\t\tcontroller,\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding controller: %w\", err)\n\t}\n\n\tif err := s.updateControllerCode(*controller); err != nil {\n\t\treturn fmt.Errorf(\"error updating controller: %w\", err)\n\t}\n\n\tdefaultMainPath := \"cmd/main.go\"\n\tif err := s.updateMainByAddingEventRecorder(defaultMainPath); err != nil {\n\t\treturn fmt.Errorf(\"error updating main.go: %w\", err)\n\t}\n\n\tif err := scaffold.Execute(\n\t\t&controllers.ControllerTest{Port: s.port},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error creating controller/**_controller_test.go: %w\", err)\n\t}\n\n\treturn s.addEnvVarIntoManager()\n}\n\n// addEnvVarIntoManager will update the config/manager/manager.yaml by adding\n// a new ENV VAR for to store the image informed which will be used in the\n// controller to create the Pod for the Kind\nfunc (s *apiScaffolder) addEnvVarIntoManager() error {\n\tmanagerPath := filepath.Join(\"config\", \"manager\", \"manager.yaml\")\n\terr := util.ReplaceInFile(managerPath, `env:`, `env:`)\n\tif err != nil {\n\t\tif err = util.InsertCode(managerPath, `name: manager`, \"\\n        env:\"); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding env key in config/manager/manager.yaml\")\n\t\t}\n\t}\n\n\tif err = util.InsertCode(managerPath, `env:`,\n\t\tfmt.Sprintf(envVarTemplate, strings.ToUpper(s.resource.Kind), s.image)); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding env key in config/manager/manager.yaml\")\n\t}\n\n\treturn nil\n}\n\n// scaffoldCreateAPI will reuse the code from the kustomize and base golang\n// plugins to do the default scaffolds which an API is created\nfunc (s *apiScaffolder) scaffoldCreateAPI() error {\n\tif err := s.scaffoldCreateAPIFromGolang(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding golang files for the new API: %w\", err)\n\t}\n\n\tif err := s.scaffoldCreateAPIFromKustomize(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding kustomize manifests for the new API: %w\", err)\n\t}\n\treturn nil\n}\n\n// TODO: replace this implementation by creating its own MainUpdater\n// which will have its own controller template which set the recorder so that we can use it\n// in the reconciliation to create an event inside for the finalizer\nfunc (s *apiScaffolder) updateMainByAddingEventRecorder(defaultMainPath string) error {\n\tif err := util.InsertCode(\n\t\tdefaultMainPath,\n\t\tfmt.Sprintf(\n\t\t\t`%sReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),`, s.resource.Kind),\n\t\tfmt.Sprintf(recorderTemplate, strings.ToLower(s.resource.Kind)),\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding event recorder in %q: %w\", defaultMainPath, err)\n\t}\n\n\treturn nil\n}\n\n// updateControllerCode will update the code generate on the template to add the Container information\nfunc (s *apiScaffolder) updateControllerCode(controller controllers.Controller) error {\n\tif err := util.ReplaceInFile(\n\t\tcontroller.Path,\n\t\t\"//TODO: scaffold container\",\n\t\tfmt.Sprintf(containerTemplate, // value for the image\n\t\t\tstrings.ToLower(s.resource.Kind), // value for the name of the container\n\t\t),\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding container in the controller path %q: %w\",\n\t\t\tcontroller.Path, err)\n\t}\n\n\t// Scaffold the command if informed\n\tif len(s.command) > 0 {\n\t\t// TODO: improve it to be an spec in the sample and api instead so that\n\t\t// users can change the values\n\t\tvar res string\n\t\tfor value := range strings.SplitSeq(s.command, \",\") {\n\t\t\tres += fmt.Sprintf(\" \\\"%s\\\",\", strings.TrimSpace(value))\n\t\t}\n\t\t// remove the latest ,\n\t\tres = res[:len(res)-1]\n\t\t// remove the first space to not fail in the go fmt ./...\n\t\tres = strings.TrimLeft(res, \" \")\n\n\t\tif err := util.InsertCode(controller.Path, `SecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},`, fmt.Sprintf(commandTemplate, res)); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding command in the  controller path %q: %w\",\n\t\t\t\tcontroller.Path, err)\n\t\t}\n\t}\n\n\t// Scaffold the port if informed\n\tif len(s.port) > 0 {\n\t\tif err := util.InsertCode(\n\t\t\tcontroller.Path,\n\t\t\t`SecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},`,\n\t\t\tfmt.Sprintf(\n\t\t\t\tportTemplate,\n\t\t\t\tstrings.ToLower(s.resource.Kind),\n\t\t\t\tstrings.ToLower(s.resource.Kind)),\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding container port in the controller path %q: %w\",\n\t\t\t\tcontroller.Path,\n\t\t\t\terr)\n\t\t}\n\t}\n\n\tif len(s.runAsUser) > 0 {\n\t\tif err := util.InsertCode(\n\t\t\tcontroller.Path,\n\t\t\t`RunAsNonRoot:             ptr.To(true),`,\n\t\t\tfmt.Sprintf(runAsUserTemplate, s.runAsUser),\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding user-id in the controller path %q: %w\",\n\t\t\t\tcontroller.Path, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *apiScaffolder) scaffoldCreateAPIFromKustomize() error {\n\tkustomizeScaffolder := kustomizev2scaffolds.NewAPIScaffolder(\n\t\ts.config,\n\t\ts.resource,\n\t\ttrue,\n\t)\n\n\tkustomizeScaffolder.InjectFS(s.fs)\n\n\tif err := kustomizeScaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding kustomize files for the APIs: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *apiScaffolder) scaffoldCreateAPIFromGolang() error {\n\tgolangV4Scaffolder := golangv4scaffolds.NewAPIScaffolder(s.config,\n\t\ts.resource, true)\n\tgolangV4Scaffolder.InjectFS(s.fs)\n\tif err := golangV4Scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding golang files for the APIs: %v\", err)\n\t}\n\n\treturn nil\n}\n\nconst containerTemplate = `Containers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"%s\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}}`\n\nconst runAsUserTemplate = `\n\t\t\t\t\t\t\tRunAsUser:                ptr.To(int64(%s)),`\n\nconst commandTemplate = `\n\t\t\t\t\t\tCommand: []string{%s},`\n\nconst portTemplate = `\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: %s.Spec.ContainerPort,\n\t\t\t\t\t\t\tName:          \"%s\",\n\t\t\t\t\t\t}},`\n\nconst recorderTemplate = `\n\t\tRecorder: mgr.GetEventRecorder(\"%s-controller\"),`\n\nconst envVarTemplate = `\n        - name: %s_IMAGE\n          value: %s`\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api/types.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Types{}\n\n// Types scaffolds the file that defines the schema for a CRD\n//\n\ntype Types struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\t// Port if informed we will create the scaffold with this spec\n\tPort string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Types) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[group]\", \"%[version]\", \"%[kind]_types.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[version]\", \"%[kind]_types.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = typesTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n//nolint:lll\nconst typesTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}\ntype {{ .Resource.Kind }}Spec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of {{ .Resource.Kind }} instances\n\t// +kubebuilder:default=1\n\t// +kubebuilder:validation:Minimum=0\n\t// +optional\n\tSize *int32 ` + \"`\" + `json:\"size,omitempty\"` + \"`\" + `\n\n\t{{ if not (isEmptyStr .Port) -}}\n\t// containerPort defines the port that will be used to init the container with the image\n\t// +required\n\tContainerPort int32 ` + \"`\" + `json:\"containerPort\"` + \"`\" + `\n\t{{- end }}\n}\n\n// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}\ntype {{ .Resource.Kind }}Status struct {\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the {{ .Resource.Kind }} resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition ` + \"`\" + `json:\"conditions,omitempty\"` + \"`\" + `\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }}\n// +kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster\n{{- else if not .Resource.API.Namespaced }}\n// +kubebuilder:resource:scope=Cluster\n{{- else if not .Resource.IsRegularPlural }}\n// +kubebuilder:resource:path={{ .Resource.Plural }}\n{{- end }}\n\n// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API\ntype {{ .Resource.Kind }} struct {\n\tmetav1.TypeMeta   ` + \"`\" + `json:\",inline\"` + \"`\" + `\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta ` + \"`\" + `json:\"metadata,omitzero\"` + \"`\" + `\n\n\t// spec defines the desired state of {{ .Resource.Kind }}\n\t// +required\n\tSpec   {{ .Resource.Kind }}Spec   ` + \"`\" + `json:\"spec\"` + \"`\" + `\n\n\t// status defines the observed state of {{ .Resource.Kind }}\n\t// +optional\n\tStatus {{ .Resource.Kind }}Status ` + \"`\" + `json:\"status,omitzero\"` + \"`\" + `\n}\n\n// +kubebuilder:object:root=true\n\n// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}\ntype {{ .Resource.Kind }}List struct {\n\tmetav1.TypeMeta ` + \"`\" + `json:\",inline\"` + \"`\" + `\n\tmetav1.ListMeta ` + \"`\" + `json:\"metadata,omitzero\"` + \"`\" + `\n\tItems           []{{ .Resource.Kind }} ` + \"`\" + `json:\"items\"` + \"`\" + `\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{})\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples/crd_sample.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage samples\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CRDSample{}\n\n// CRDSample scaffolds a file that defines a sample manifest for the CRD\ntype CRDSample struct {\n\tmachinery.TemplateMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\n\t// Port if informed we will create the scaffold with this spec\n\tPort string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CRDSample) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"config\", \"samples\", \"%[group]_%[version]_%[kind].yaml\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"config\", \"samples\", \"%[version]_%[kind].yaml\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\tf.TemplateBody = crdSampleTemplate\n\n\treturn nil\n}\n\nconst crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }}\nkind: {{ .Resource.Kind }}\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ .ProjectName }}\n    app.kubernetes.io/managed-by: kustomize\n  name: {{ lower .Resource.Kind }}-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n{{ if not (isEmptyStr .Port) }}\n  # TODO(user): edit the following value to ensure the container has the right port to be initialized\n  containerPort: {{ .Port }}\n{{ end -}}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ControllerTest{}\n\n// ControllerTest scaffolds the file that defines tests for the controller for a CRD or a builtin resource\n//\n\ntype ControllerTest struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\tPort        string\n\tPackageName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ControllerTest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[group]\", \"%[kind]_controller_test.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[kind]_controller_test.go\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.PackageName = \"controller\"\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\tlog.Info(\"creating import for resource\", \"resource\", f.Resource.Path)\n\tf.TemplateBody = controllerTestTemplate\n\n\treturn nil\n}\n\nconst controllerTestTemplate = `{{ .Boilerplate }}\n\npackage {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}{{ .PackageName }}{{ end }}\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n)\n\nvar _ = Describe(\"{{ .Resource.Kind }} controller\", func() {\n\tContext(\"{{ .Resource.Kind }} controller test\", func() {\n\n\t\tconst {{ .Resource.Kind }}Name = \"test-{{ lower .Resource.Kind }}\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      {{ .Resource.Kind }}Name,\n\t\t\t\tNamespace: {{ .Resource.Kind }}Name,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      {{ .Resource.Kind }}Name,\n\t\t\tNamespace: {{ .Resource.Kind }}Name,\n\t\t}\n\t\t{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Create(ctx, namespace);\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Setting the Image ENV VAR which stores the Operand image\")\n\t\t\terr= os.Setenv(\"{{ upper .Resource.Kind }}_IMAGE\", \"example.com/image:test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating the custom resource for the Kind {{ .Resource.Kind }}\")\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t// Let's mock our custom resource at the same way that we would\n\t\t\t\t// apply on the cluster the manifest under config/samples\n\t\t\t\t{{ lower .Resource.Kind }} = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      {{ .Resource.Kind }}Name,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: {{ .Resource.ImportAlias }}.{{ .Resource.Kind }}Spec{\n\t\t\t\t\t\tSize: ptr.To(int32(1)),\n\t\t\t\t\t\t{{ if not (isEmptyStr .Port) -}}\n\t\t\t\t\t\tContainerPort: {{ .Port }},\n\t\t\t\t\t\t{{- end }}\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, {{ lower .Resource.Kind }})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind {{ .Resource.Kind }}\")\n\t\t\tfound := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace);\n\n\t\t\tBy(\"Removing the Image ENV VAR which stores the Operand image\")\n\t\t\t_ = os.Unsetenv(\"{{ upper .Resource.Kind }}_IMAGE\")\n\t\t})\n\n\t\tIt(\"should successfully reconcile a custom resource for {{ .Resource.Kind }}\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource created\")\n\t\t\t{{ lower .Resource.Kind }}Reconciler := &{{ .Resource.Kind }}Reconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := {{ lower .Resource.Kind }}Reconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = {{ lower .Resource.Kind }}Reconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the {{ .Resource.Kind }} instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect({{ lower .Resource.Kind }}.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailable{{ .Resource.Kind }})), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailable{{ .Resource.Kind }})\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailable{{ .Resource.Kind }})\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailable{{ .Resource.Kind }})\n\t\t})\n\t})\n})\n`\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Controller{}\n\n// Controller scaffolds the file that defines the controller for a CRD or a builtin resource\n//\n\ntype Controller struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\tControllerRuntimeVersion string\n\n\tPackageName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Controller) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[group]\", \"%[kind]_controller.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[kind]_controller.go\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.PackageName = \"controller\"\n\n\tlog.Info(\"creating import for resource\", \"resource_path\", f.Resource.Path)\n\tf.TemplateBody = controllerTemplate\n\n\t// This one is to overwrite the controller if it exist\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n//nolint:lll\nconst controllerTemplate = `{{ .Boilerplate }}\n\npackage {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}{{ .PackageName }}{{ end }}\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\t\"fmt\"\n\t\"os\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\t\"k8s.io/client-go/tools/events\"\n\t\"k8s.io/utils/ptr\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n)\n\nconst {{ lower .Resource.Kind }}Finalizer = \"{{ .Resource.Group }}.{{ .Resource.Domain }}/finalizer\"\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailable{{ .Resource.Kind }} represents the status of the Deployment reconciliation\n\ttypeAvailable{{ .Resource.Kind }} = \"Available\"\n\t// typeDegraded{{ .Resource.Kind }} represents the status used when the custom resource is deleted and the finalizer operations are yet to occur.\n\ttypeDegraded{{ .Resource.Kind }} = \"Degraded\"\n)\n\n// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object\ntype {{ .Resource.Kind }}Reconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n\tRecorder events.EventRecorder\n}\n\n// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen\n// when the command <make manifests> is executed.\n// To know more about markers see: https://book.kubebuilder.io/reference/markers.html\n\n{{ if .Namespaced -}}\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }}/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }}/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,namespace={{ .ProjectName }}-system,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,namespace={{ .ProjectName }}-system,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,namespace={{ .ProjectName }}-system,resources=pods,verbs=get;list;watch\n{{- else -}}\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n{{- end }}\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile\nfunc (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the {{ .Resource.Kind }} instance\n\t// The purpose is check if the Custom Resource for the Kind {{ .Resource.Kind }}\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\t{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\terr := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }})\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"{{ .Resource.Kind }} resource not found, ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get {{ lower .Resource.Kind }}\")\n\t\treturn ctrl.Result{}, err\n\t}\n\t\n\tif len({{ lower .Resource.Kind }}.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }}, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the {{ lower .Resource.Kind }} Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch {{ lower .Resource.Kind }}\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Let's add a finalizer. Then, we can define some operations which should\n\t// occur before the custom resource is deleted.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers\n\tif !controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {\n\t\tlog.Info(\"Adding finalizer for {{ .Resource.Kind }}\")\n\t\tcontrollerutil.AddFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer)\n\t\tif err = r.Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\tlog.Error(err, \"Failed to update custom resource to add finalizer\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the {{ .Resource.Kind }} instance is marked to be deleted, which is\n\t// indicated by the deletion timestamp being set.\n\tis{{ .Resource.Kind }}MarkedToBeDeleted := {{ lower .Resource.Kind }}.GetDeletionTimestamp() != nil\n\tif is{{ .Resource.Kind }}MarkedToBeDeleted {\n\t\tif controllerutil.ContainsFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer) {\n\t\t\tlog.Info(\"Performing finalizer operations for {{ .Resource.Kind }} before deleting CR\")\n\n\t\t\t// Let's add here a status \"Downgrade\" to reflect that this resource began its process to be terminated.\n\t\t\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeDegraded{{ .Resource.Kind }},\n\t\t\t\tStatus: metav1.ConditionUnknown, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Performing finalizer operations for the custom resource: %s \", {{ lower .Resource.Kind }}.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// Perform all operations required before removing the finalizer and allow\n\t\t\t// the Kubernetes API to remove the custom resource.\n\t\t\tr.doFinalizerOperationsFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})\n\n\t\t\t// TODO(user): If you add operations to the doFinalizerOperationsFor{{ .Resource.Kind }} method\n\t\t\t// then you need to ensure that all worked fine before deleting and updating the Downgrade status\n\t\t\t// otherwise, you should requeue here.\n\n\t\t\t// Re-fetch the {{ lower .Resource.Kind }} Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch {{ lower .Resource.Kind }}\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeDegraded{{ .Resource.Kind }},\n\t\t\t\tStatus: metav1.ConditionTrue, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Finalizer operations for custom resource %s name were successfully accomplished\", {{ lower .Resource.Kind }}.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing finalizer for {{ .Resource.Kind }} after successfully performing the operations\")\n\t\t\tif ok:= controllerutil.RemoveFinalizer({{ lower .Resource.Kind }}, {{ lower .Resource.Kind }}Finalizer); !ok{\n\t\t\t\terr = fmt.Errorf(\"finalizer for {{ .Resource.Kind }} was not removed\")\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for {{ .Resource.Kind }}\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tif err := r.Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for {{ .Resource.Kind }}\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: {{ lower .Resource.Kind }}.Name, Namespace: {{ lower .Resource.Kind }}.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentFor{{ .Resource.Kind }}({{ lower .Resource.Kind }})\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for {{ .Resource.Kind }}\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", {{ lower .Resource.Kind }}.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-triggered again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif {{ lower .Resource.Kind }}.Spec.Size != nil {\n\t\tdesiredReplicas = *{{ lower .Resource.Kind }}.Spec.Size\n\t}\n\n\t// The CRD API defines that the {{ .Resource.Kind }} type have a {{ .Resource.Kind }}Spec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the {{ lower .Resource.Kind }} Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch {{ lower .Resource.Kind }}\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", {{ lower .Resource.Kind }}.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&{{ lower .Resource.Kind }}.Status.Conditions, metav1.Condition{Type: typeAvailable{{ .Resource.Kind }},\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", {{ lower .Resource.Kind }}.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, {{ lower .Resource.Kind }}); err != nil {\n\t\tlog.Error(err, \"Failed to update {{ .Resource.Kind }} status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// finalize{{ .Resource.Kind }} will perform the required operations before delete the CR.\nfunc (r *{{ .Resource.Kind }}Reconciler) doFinalizerOperationsFor{{ .Resource.Kind }}(cr *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) {\n\t// TODO(user): Add the cleanup steps that the operator\n\t// needs to do before the CR can be deleted. Examples\n\t// of finalizers include performing backups and deleting\n\t// resources that are not owned by this CR, like a PVC.\n\n\t// Note: It is not recommended to use finalizers with the purpose of deleting resources which are\n\t// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,\n\t// are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference.\n\t// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.\n\t// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/\n\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name,\n\t\tcr.Namespace)\n}\n\n// deploymentFor{{ .Resource.Kind }} returns a {{ .Resource.Kind }} Deployment object\nfunc (r *{{ .Resource.Kind }}Reconciler) deploymentFor{{ .Resource.Kind }}(\n\t{{ lower .Resource.Kind }} *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (*appsv1.Deployment, error) {\n\tls := labelsFor{{ .Resource.Kind }}()\n\n\t// Get the Operand image\n\timage, err := imageFor{{ .Resource.Kind }}()\n\tif err != nil {\n    \treturn nil, err\n\t}\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      {{ lower .Resource.Kind }}.Name,\n\t\t\tNamespace: {{ lower .Resource.Kind }}.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: {{ lower .Resource.Kind }}.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t// TODO(user): Uncomment the following code to configure the nodeAffinity expression\n\t\t\t\t\t// according to the platforms which are supported by your solution. It is considered\n\t\t\t\t\t// best practice to support multiple architectures. build your manager image using the\n\t\t\t\t\t// makefile target docker-buildx. Also, you can use docker manifest inspect <image>\n\t\t\t\t\t// to check what are the platforms supported.\n\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n\t\t\t\t\t// Affinity: &corev1.Affinity{\n\t\t\t\t\t//\t NodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t//\t\t RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t//\t\t\t NodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t//\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t MatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/arch\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/os\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"linux\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t },\n\t\t\t\t\t//\t\t \t },\n\t\t\t\t\t//\t\t },\n\t\t\t\t\t//\t },\n\t\t\t\t\t// },\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\t// IMPORTANT: seccomProfile was introduced with Kubernetes 1.19\n\t\t\t\t\t\t// If you are looking for to produce solutions to be supported\n\t\t\t\t\t\t// on lower versions you must remove this option.\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t//TODO: scaffold container,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference({{ lower .Resource.Kind }}, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n\n// labelsFor{{ .Resource.Kind }} returns the labels for selecting the resources\n// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\nfunc labelsFor{{ .Resource.Kind }}() map[string]string {\n\tvar imageTag string\n\timage, err := imageFor{{ .Resource.Kind }}()\n\tif err == nil {\n\t\timageTag = strings.Split(image, \":\")[1]\n\t}\n\treturn map[string]string{\n\t\t\"app.kubernetes.io/name\": \"{{ .ProjectName }}\",\n\t\t\"app.kubernetes.io/version\": imageTag,\n\t\t\"app.kubernetes.io/managed-by\": \"{{ .Resource.Kind }}Controller\",\n\t}\n}\n\n// imageFor{{ .Resource.Kind }} gets the Operand image which is managed by this controller\n// from the {{ upper .Resource.Kind }}_IMAGE environment variable defined in the config/manager/manager.yaml\nfunc imageFor{{ .Resource.Kind }}() (string, error) {\n\tvar imageEnvVar = \"{{ upper .Resource.Kind }}_IMAGE\"\n    image, found := os.LookupEnv(imageEnvVar)\n    if !found {\n        return \"\", fmt.Errorf(\"unable to find %s environment variable with the image\", imageEnvVar)\n    }\n    return image, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\n// The whole idea is to be watching the resources that matter for the controller.\n// When a resource that the controller is interested in changes, the Watch triggers\n// the controller’s reconciliation loop, ensuring that the actual state of the resource\n// matches the desired state as defined in the controller’s logic.\n//\n// Notice how we configured the Manager to monitor events such as the creation, update,\n// or deletion of a Custom Resource (CR) of the {{ .Resource.Kind }} kind, as well as any changes\n// to the Deployment that the controller manages and owns.\nfunc (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t\t// Watch the {{ .Resource.Kind }} CR(s) and trigger reconciliation whenever it\n\t\t// is created, updated, or deleted\n\t\tFor(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).\n\t\t{{- else -}}\n\t\t// Uncomment the following line adding a pointer to an instance of the controlled resource as an argument\n\t\t// For().\n\t\t{{- end }}\n\t\t{{- if and (.MultiGroup) (not (isEmptyStr .Resource.Group)) }}\n\t\tNamed(\"{{ lower .Resource.Group }}-{{ lower .Resource.Kind }}\").\n\t\t{{- else }}\n\t\tNamed(\"{{ lower .Resource.Kind }}\").\n\t\t{{- end }}\n\t\t// Watch the Deployment managed by the {{ .Resource.Kind }}Reconciler. If any changes occur to the Deployment\n\t\t// owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n\t\t// state aligns with the desired state. See that the ownerRef was set when the Deployment was created.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/deploy-image/v1alpha1/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestDeployImageV1Alpha1(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Deploy Image V1Alpha1 Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/golang/domain.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport \"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\n// DefaultNameQualifier is the suffix appended to all kubebuilder plugin names for Golang operators.\nconst DefaultNameQualifier = \"go.\" + plugins.DefaultNameQualifier\n"
  },
  {
    "path": "pkg/plugins/golang/go_version.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst (\n\tgoVerPattern = `^go(?P<major>[0-9]+)\\.(?P<minor>[0-9]+)(?:\\.(?P<patch>[0-9]+)|(?P<pre>(?:alpha|beta|rc)[0-9]+))?$`\n)\n\nvar goVerRegexp = regexp.MustCompile(goVerPattern)\n\n// GoVersion describes a Go version.\ntype GoVersion struct {\n\tmajor, minor, patch int\n\tprerelease          string\n}\n\nfunc (v GoVersion) String() string {\n\tswitch {\n\tcase v.patch != 0:\n\t\treturn fmt.Sprintf(\"go%d.%d.%d\", v.major, v.minor, v.patch)\n\tcase v.prerelease != \"\":\n\t\treturn fmt.Sprintf(\"go%d.%d%s\", v.major, v.minor, v.prerelease)\n\t}\n\treturn fmt.Sprintf(\"go%d.%d\", v.major, v.minor)\n}\n\n// MustParse will panic if verStr does not match the expected Go version string spec.\nfunc MustParse(verStr string) (v GoVersion) {\n\tif err := v.parse(verStr); err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\nfunc (v *GoVersion) parse(verStr string) error {\n\tm := goVerRegexp.FindStringSubmatch(verStr)\n\tif m == nil {\n\t\treturn fmt.Errorf(\"invalid version string\")\n\t}\n\n\tvar err error\n\n\tv.major, err = strconv.Atoi(m[1])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing major version %q: %w\", m[1], err)\n\t}\n\n\tv.minor, err = strconv.Atoi(m[2])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing minor version %q: %w\", m[2], err)\n\t}\n\n\tif m[3] != \"\" {\n\t\tv.patch, err = strconv.Atoi(m[3])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing patch version %q: %w\", m[2], err)\n\t\t}\n\t}\n\n\tv.prerelease = m[4]\n\n\treturn nil\n}\n\n// Compare returns -1, 0, or 1 if v < other, v == other, or v > other, respectively.\nfunc (v GoVersion) Compare(other GoVersion) int {\n\tif v.major > other.major {\n\t\treturn 1\n\t}\n\tif v.major < other.major {\n\t\treturn -1\n\t}\n\n\tif v.minor > other.minor {\n\t\treturn 1\n\t}\n\tif v.minor < other.minor {\n\t\treturn -1\n\t}\n\n\tif v.patch > other.patch {\n\t\treturn 1\n\t}\n\tif v.patch < other.patch {\n\t\treturn -1\n\t}\n\n\tif v.prerelease == other.prerelease {\n\t\treturn 0\n\t}\n\tif v.prerelease == \"\" {\n\t\treturn 1\n\t}\n\tif other.prerelease == \"\" {\n\t\treturn -1\n\t}\n\tif v.prerelease > other.prerelease {\n\t\treturn 1\n\t}\n\treturn -1\n}\n\n// ValidateGoVersion verifies that Go is installed and the current go version is supported by a plugin.\nfunc ValidateGoVersion(minVersion, maxVersion GoVersion) error {\n\terr := fetchAndCheckGoVersion(minVersion, maxVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"you can skip this check using the --skip-go-version-check flag: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc fetchAndCheckGoVersion(minVersion, maxVersion GoVersion) error {\n\tcmd := exec.Command(\"go\", \"version\")\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve 'go version': %v\", string(out))\n\t}\n\n\tsplit := strings.Split(string(out), \" \")\n\tif len(split) < 3 {\n\t\treturn fmt.Errorf(\"found invalid Go version: %q\", string(out))\n\t}\n\tgoVer := split[2]\n\tif err := checkGoVersion(goVer, minVersion, maxVersion); err != nil {\n\t\treturn fmt.Errorf(\"go version %q is incompatible: %w\", goVer, err)\n\t}\n\treturn nil\n}\n\nfunc checkGoVersion(verStr string, minVersion, maxVersion GoVersion) error {\n\tvar version GoVersion\n\tif err := version.parse(verStr); err != nil {\n\t\treturn err\n\t}\n\n\tif version.Compare(minVersion) < 0 || version.Compare(maxVersion) >= 0 {\n\t\treturn fmt.Errorf(\"plugin requires %q <= version < %q\", minVersion, maxVersion)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/go_version_test.go",
    "content": "/*\nCopyright 2018 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"errors\"\n\t\"slices\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"GoVersion\", func() {\n\tContext(\"String\", func() {\n\t\tIt(\"patch is not empty\", func() {\n\t\t\tv := GoVersion{major: 1, minor: 1, patch: 1}\n\t\t\tExpect(v.String()).To(Equal(\"go1.1.1\"))\n\t\t})\n\t\tIt(\"preRelease is not empty\", func() {\n\t\t\tv := GoVersion{major: 1, minor: 1, prerelease: \"-alpha\"}\n\t\t\tExpect(v.String()).To(Equal(\"go1.1-alpha\"))\n\t\t})\n\t\tIt(\"default\", func() {\n\t\t\tv := GoVersion{major: 1, minor: 1}\n\t\t\tExpect(v.String()).To(Equal(\"go1.1\"))\n\t\t})\n\t})\n\n\tContext(\"MustParse\", func() {\n\t\tIt(\"succeeds\", func() {\n\t\t\tv := GoVersion{major: 1, minor: 1, patch: 1}\n\t\t\tExpect(MustParse(\"go1.1.1\")).To(Equal(v))\n\t\t})\n\t\tIt(\"panics\", func() {\n\t\t\ttriggerPanic := func() {\n\t\t\t\tMustParse(\"go1.a\")\n\t\t\t}\n\t\t\tExpect(triggerPanic).To(PanicWith(errors.New(\"invalid version string\")))\n\t\t})\n\t})\n\n\tContext(\"parse\", func() {\n\t\tvar v GoVersion\n\n\t\tBeforeEach(func() {\n\t\t\tv = GoVersion{}\n\t\t})\n\n\t\tDescribeTable(\"should succeed for valid versions\",\n\t\t\tfunc(version string, expected GoVersion) {\n\t\t\t\tExpect(v.parse(version)).NotTo(HaveOccurred())\n\t\t\t\tExpect(v.major).To(Equal(expected.major))\n\t\t\t\tExpect(v.minor).To(Equal(expected.minor))\n\t\t\t\tExpect(v.patch).To(Equal(expected.patch))\n\t\t\t\tExpect(v.prerelease).To(Equal(expected.prerelease))\n\t\t\t},\n\t\t\tEntry(\"for minor release\", \"go1.15\", GoVersion{\n\t\t\t\tmajor: 1,\n\t\t\t\tminor: 15,\n\t\t\t}),\n\t\t\tEntry(\"for patch release\", \"go1.15.1\", GoVersion{\n\t\t\t\tmajor: 1,\n\t\t\t\tminor: 15,\n\t\t\t\tpatch: 1,\n\t\t\t}),\n\t\t\tEntry(\"for alpha release\", \"go1.15alpha1\", GoVersion{\n\t\t\t\tmajor:      1,\n\t\t\t\tminor:      15,\n\t\t\t\tprerelease: \"alpha1\",\n\t\t\t}),\n\t\t\tEntry(\"for beta release\", \"go1.15beta1\", GoVersion{\n\t\t\t\tmajor:      1,\n\t\t\t\tminor:      15,\n\t\t\t\tprerelease: \"beta1\",\n\t\t\t}),\n\t\t\tEntry(\"for release candidate\", \"go1.15rc1\", GoVersion{\n\t\t\t\tmajor:      1,\n\t\t\t\tminor:      15,\n\t\t\t\tprerelease: \"rc1\",\n\t\t\t}),\n\t\t)\n\n\t\tDescribeTable(\"should fail for invalid versions\",\n\t\t\tfunc(version string) { Expect(v.parse(version)).To(HaveOccurred()) },\n\t\t\tEntry(\"for invalid prefix\", \"g1.15\"),\n\t\t\tEntry(\"for missing major version\", \"go.15\"),\n\t\t\tEntry(\"for missing minor version\", \"go1.\"),\n\t\t\tEntry(\"for patch and prerelease version\", \"go1.15.1rc1\"),\n\t\t\tEntry(\"for invalid major version\", \"goa.15\"),\n\t\t\tEntry(\"for invalid minor version\", \"go1.a\"),\n\t\t\tEntry(\"for invalid patch version\", \"go1.15.a\"),\n\t\t)\n\t})\n\n\tContext(\"Compare\", func() {\n\t\t// Test Compare() by sorting a list.\n\t\tvar (\n\t\t\tversions       []GoVersion\n\t\t\tsortedVersions []GoVersion\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tversions = []GoVersion{\n\t\t\t\t{major: 1, minor: 15, prerelease: \"rc2\"},\n\t\t\t\t{major: 1, minor: 15, patch: 1},\n\t\t\t\t{major: 1, minor: 16},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"beta1\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"alpha2\"},\n\t\t\t\t{major: 2, minor: 0},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"alpha1\"},\n\t\t\t\t{major: 1, minor: 13},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"rc1\"},\n\t\t\t\t{major: 1, minor: 15},\n\t\t\t\t{major: 1, minor: 15, patch: 2},\n\t\t\t\t{major: 1, minor: 14},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"beta2\"},\n\t\t\t\t{major: 0, minor: 123},\n\t\t\t}\n\n\t\t\tsortedVersions = []GoVersion{\n\t\t\t\t{major: 0, minor: 123},\n\t\t\t\t{major: 1, minor: 13},\n\t\t\t\t{major: 1, minor: 14},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"alpha1\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"alpha2\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"beta1\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"beta2\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"rc1\"},\n\t\t\t\t{major: 1, minor: 15, prerelease: \"rc2\"},\n\t\t\t\t{major: 1, minor: 15},\n\t\t\t\t{major: 1, minor: 15, patch: 1},\n\t\t\t\t{major: 1, minor: 15, patch: 2},\n\t\t\t\t{major: 1, minor: 16},\n\t\t\t\t{major: 2, minor: 0},\n\t\t\t}\n\t\t})\n\n\t\tIt(\"sorts a valid list of versions correctly\", func() {\n\t\t\tslices.SortStableFunc(versions, func(a, b GoVersion) int {\n\t\t\t\treturn a.Compare(b)\n\t\t\t})\n\t\t\tExpect(versions).To(Equal(sortedVersions))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"ValidateGoVersion\", func() {\n\tDescribeTable(\"should return no error for valid/supported go versions\", func(minVersion, maxVersion GoVersion) {\n\t\tExpect(ValidateGoVersion(minVersion, maxVersion)).To(Succeed())\n\t},\n\t\tEntry(\"for minVersion: 1.1.1 and maxVersion: 2000.1.1\", GoVersion{major: 1, minor: 1, patch: 1},\n\t\t\tGoVersion{major: 2000, minor: 1, patch: 1}),\n\t\tEntry(\"for minVersion: 1.1.1 and maxVersion: 1.2000.2000\", GoVersion{major: 1, minor: 1, patch: 1},\n\t\t\tGoVersion{major: 1, minor: 2000, patch: 1}),\n\t)\n\n\tDescribeTable(\"should return error for invalid/unsupported go versions\", func(minVersion, maxVersion GoVersion) {\n\t\tExpect(ValidateGoVersion(minVersion, maxVersion)).NotTo(Succeed())\n\t},\n\t\tEntry(\"for invalid min and maxVersions\", GoVersion{major: 2, minor: 2, patch: 2},\n\t\t\tGoVersion{major: 1, minor: 1, patch: 1}),\n\t)\n})\n\nvar _ = Describe(\"checkGoVersion\", func() {\n\tvar (\n\t\tgoVerMin GoVersion\n\t\tgoVerMax GoVersion\n\t)\n\n\tBeforeEach(func() {\n\t\tgoVerMin = MustParse(\"go1.13\")\n\t\tgoVerMax = MustParse(\"go2.0alpha1\")\n\t})\n\n\tDescribeTable(\"should return no error for supported go versions\",\n\t\tfunc(version string) { Expect(checkGoVersion(version, goVerMin, goVerMax)).To(Succeed()) },\n\t\tEntry(\"for go 1.13\", \"go1.13\"),\n\t\tEntry(\"for go 1.13.1\", \"go1.13.1\"),\n\t\tEntry(\"for go 1.13.2\", \"go1.13.2\"),\n\t\tEntry(\"for go 1.13.3\", \"go1.13.3\"),\n\t\tEntry(\"for go 1.13.4\", \"go1.13.4\"),\n\t\tEntry(\"for go 1.13.5\", \"go1.13.5\"),\n\t\tEntry(\"for go 1.13.6\", \"go1.13.6\"),\n\t\tEntry(\"for go 1.13.7\", \"go1.13.7\"),\n\t\tEntry(\"for go 1.13.8\", \"go1.13.8\"),\n\t\tEntry(\"for go 1.13.9\", \"go1.13.9\"),\n\t\tEntry(\"for go 1.13.10\", \"go1.13.10\"),\n\t\tEntry(\"for go 1.13.11\", \"go1.13.11\"),\n\t\tEntry(\"for go 1.13.12\", \"go1.13.12\"),\n\t\tEntry(\"for go 1.13.13\", \"go1.13.13\"),\n\t\tEntry(\"for go 1.13.14\", \"go1.13.14\"),\n\t\tEntry(\"for go 1.13.15\", \"go1.13.15\"),\n\t\tEntry(\"for go 1.14beta1\", \"go1.14beta1\"),\n\t\tEntry(\"for go 1.14rc1\", \"go1.14rc1\"),\n\t\tEntry(\"for go 1.14\", \"go1.14\"),\n\t\tEntry(\"for go 1.14.1\", \"go1.14.1\"),\n\t\tEntry(\"for go 1.14.2\", \"go1.14.2\"),\n\t\tEntry(\"for go 1.14.3\", \"go1.14.3\"),\n\t\tEntry(\"for go 1.14.4\", \"go1.14.4\"),\n\t\tEntry(\"for go 1.14.5\", \"go1.14.5\"),\n\t\tEntry(\"for go 1.14.6\", \"go1.14.6\"),\n\t\tEntry(\"for go 1.14.7\", \"go1.14.7\"),\n\t\tEntry(\"for go 1.14.8\", \"go1.14.8\"),\n\t\tEntry(\"for go 1.14.9\", \"go1.14.9\"),\n\t\tEntry(\"for go 1.14.10\", \"go1.14.10\"),\n\t\tEntry(\"for go 1.14.11\", \"go1.14.11\"),\n\t\tEntry(\"for go 1.14.12\", \"go1.14.12\"),\n\t\tEntry(\"for go 1.14.13\", \"go1.14.13\"),\n\t\tEntry(\"for go 1.14.14\", \"go1.14.14\"),\n\t\tEntry(\"for go 1.14.15\", \"go1.14.15\"),\n\t\tEntry(\"for go 1.15beta1\", \"go1.15beta1\"),\n\t\tEntry(\"for go 1.15rc1\", \"go1.15rc1\"),\n\t\tEntry(\"for go 1.15rc2\", \"go1.15rc2\"),\n\t\tEntry(\"for go 1.15\", \"go1.15\"),\n\t\tEntry(\"for go 1.15.1\", \"go1.15.1\"),\n\t\tEntry(\"for go 1.15.2\", \"go1.15.2\"),\n\t\tEntry(\"for go 1.15.3\", \"go1.15.3\"),\n\t\tEntry(\"for go 1.15.4\", \"go1.15.4\"),\n\t\tEntry(\"for go 1.15.5\", \"go1.15.5\"),\n\t\tEntry(\"for go 1.15.6\", \"go1.15.6\"),\n\t\tEntry(\"for go 1.15.7\", \"go1.15.7\"),\n\t\tEntry(\"for go 1.15.8\", \"go1.15.8\"),\n\t\tEntry(\"for go 1.16\", \"go1.16\"),\n\t\tEntry(\"for go 1.16.1\", \"go1.16.1\"),\n\t\tEntry(\"for go 1.16.2\", \"go1.16.2\"),\n\t\tEntry(\"for go 1.16.3\", \"go1.16.3\"),\n\t\tEntry(\"for go 1.16.4\", \"go1.16.4\"),\n\t\tEntry(\"for go 1.16.5\", \"go1.16.5\"),\n\t\tEntry(\"for go 1.16.6\", \"go1.16.6\"),\n\t\tEntry(\"for go 1.16.7\", \"go1.16.7\"),\n\t\tEntry(\"for go 1.16.8\", \"go1.16.8\"),\n\t\tEntry(\"for go 1.16.9\", \"go1.16.9\"),\n\t\tEntry(\"for go 1.16.10\", \"go1.16.10\"),\n\t\tEntry(\"for go 1.16.11\", \"go1.16.11\"),\n\t\tEntry(\"for go 1.16.12\", \"go1.16.12\"),\n\t\tEntry(\"for go 1.17.1\", \"go1.17.1\"),\n\t\tEntry(\"for go 1.17.2\", \"go1.17.2\"),\n\t\tEntry(\"for go 1.17.3\", \"go1.17.3\"),\n\t\tEntry(\"for go 1.17.4\", \"go1.17.4\"),\n\t\tEntry(\"for go 1.17.5\", \"go1.17.5\"),\n\t\tEntry(\"for go 1.18.1\", \"go1.18.1\"),\n\t\tEntry(\"for go.1.19\", \"go1.19\"),\n\t\tEntry(\"for go.1.19.1\", \"go1.19.1\"),\n\t\tEntry(\"for go.1.20\", \"go1.20\"),\n\t\tEntry(\"for go.1.21\", \"go1.21\"),\n\t\tEntry(\"for go.1.22\", \"go1.22\"),\n\t\tEntry(\"for go.1.23\", \"go1.23\"),\n\t\tEntry(\"for go.1.24\", \"go1.24\"),\n\t\tEntry(\"for go.1.25\", \"go1.25\"),\n\t)\n\n\tDescribeTable(\"should return an error for non-supported go versions\",\n\t\tfunc(version string) { Expect(checkGoVersion(version, goVerMin, goVerMax)).NotTo(Succeed()) },\n\t\tEntry(\"for invalid go versions\", \"go\"),\n\t\tEntry(\"for go 1.13beta1\", \"go1.13beta1\"),\n\t\tEntry(\"for go 1.13rc1\", \"go1.13rc1\"),\n\t\tEntry(\"for go 1.13rc2\", \"go1.13rc2\"),\n\t\tEntry(\"for go 2.0alpha1\", \"go2.0alpha1\"),\n\t\tEntry(\"for go 2.0.0\", \"go2.0.0\"),\n\t)\n})\n"
  },
  {
    "path": "pkg/plugins/golang/options.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"path\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar coreGroups = map[string]string{\n\t\"admission\":             \"k8s.io\",\n\t\"admissionregistration\": \"k8s.io\",\n\t\"apps\":                  \"\",\n\t\"auditregistration\":     \"k8s.io\",\n\t\"apiextensions\":         \"k8s.io\",\n\t\"authentication\":        \"k8s.io\",\n\t\"authorization\":         \"k8s.io\",\n\t\"autoscaling\":           \"\",\n\t\"batch\":                 \"\",\n\t\"certificates\":          \"k8s.io\",\n\t\"coordination\":          \"k8s.io\",\n\t\"core\":                  \"\",\n\t\"events\":                \"k8s.io\",\n\t\"extensions\":            \"\",\n\t\"imagepolicy\":           \"k8s.io\",\n\t\"networking\":            \"k8s.io\",\n\t\"node\":                  \"k8s.io\",\n\t\"metrics\":               \"k8s.io\",\n\t\"policy\":                \"\",\n\t\"rbac.authorization\":    \"k8s.io\",\n\t\"scheduling\":            \"k8s.io\",\n\t\"setting\":               \"k8s.io\",\n\t\"storage\":               \"k8s.io\",\n}\n\n// Options contains the information required to build a new resource.Resource.\ntype Options struct {\n\t// Plural is the resource's kind plural form.\n\tPlural string\n\n\t// ExternalAPIPath allows to inform a path for APIs not defined in the project\n\tExternalAPIPath string\n\n\t// ExternalAPIDomain allows to inform the resource domain to build the Qualified Group\n\t// to generate the RBAC markers\n\tExternalAPIDomain string\n\n\t// ExternalAPIModule specifies the Go module path for the external API with optional version.\n\t// Example: github.com/cert-manager/cert-manager@v1.18.2\n\tExternalAPIModule string\n\n\t// Namespaced is true if the resource should be namespaced.\n\tNamespaced bool\n\n\t// Flags that define which parts should be scaffolded\n\tDoAPI        bool\n\tDoController bool\n\tDoDefaulting bool\n\tDoValidation bool\n\tDoConversion bool\n\n\t// Spoke versions for conversion webhook\n\tSpoke []string\n\n\t// DefaultingPath is the custom path for the defaulting/mutating webhook\n\tDefaultingPath string\n\n\t// ValidationPath is the custom path for the validation webhook\n\tValidationPath string\n}\n\n// UpdateResource updates the provided resource with the options\nfunc (opts Options) UpdateResource(res *resource.Resource, c config.Config) {\n\tif opts.Plural != \"\" {\n\t\tres.Plural = opts.Plural\n\t}\n\n\tif opts.DoAPI {\n\t\tres.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup())\n\n\t\tres.API = &resource.API{\n\t\t\tCRDVersion: \"v1\",\n\t\t\tNamespaced: opts.Namespaced,\n\t\t}\n\t}\n\n\tif opts.DoController {\n\t\tres.Controller = true\n\t}\n\n\tif opts.DoDefaulting || opts.DoValidation || opts.DoConversion {\n\t\tres.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup())\n\n\t\tres.Webhooks.WebhookVersion = \"v1\"\n\t\tif opts.DoDefaulting {\n\t\t\tres.Webhooks.Defaulting = true\n\t\t\tif opts.DefaultingPath != \"\" {\n\t\t\t\tres.Webhooks.DefaultingPath = opts.DefaultingPath\n\t\t\t}\n\t\t}\n\t\tif opts.DoValidation {\n\t\t\tres.Webhooks.Validation = true\n\t\t\tif opts.ValidationPath != \"\" {\n\t\t\t\tres.Webhooks.ValidationPath = opts.ValidationPath\n\t\t\t}\n\t\t}\n\t\tif opts.DoConversion {\n\t\t\tres.Webhooks.Conversion = true\n\t\t\tres.Webhooks.Spoke = opts.Spoke\n\t\t}\n\t}\n\n\tif len(opts.ExternalAPIPath) > 0 {\n\t\tres.External = true\n\t\tres.Path = opts.ExternalAPIPath\n\t\tif len(opts.ExternalAPIDomain) > 0 {\n\t\t\tres.Domain = opts.ExternalAPIDomain\n\t\t}\n\t\t// Store module path if provided\n\t\tif len(opts.ExternalAPIModule) > 0 {\n\t\t\tres.Module = opts.ExternalAPIModule\n\t\t}\n\t}\n\n\t// domain and path may need to be changed in case we are referring to a builtin core resource:\n\t//  - Check if we are scaffolding the resource now           => project resource\n\t//  - Check if we already scaffolded the resource            => project resource\n\t//  - Check if the resource group is a well-known core group => builtin core resource\n\t//  - In any other case, default to                          => project resource\n\tif !opts.DoAPI {\n\t\tvar alreadyHasAPI bool\n\t\tloadedRes, err := c.GetResource(res.GVK)\n\t\talreadyHasAPI = err == nil && loadedRes.HasAPI()\n\t\tif !alreadyHasAPI {\n\t\t\tif res.External {\n\t\t\t\tres.Path = opts.ExternalAPIPath\n\t\t\t\tres.Domain = opts.ExternalAPIDomain\n\t\t\t} else {\n\t\t\t\t// Handle core types\n\t\t\t\tif domain, found := coreGroups[res.Group]; found {\n\t\t\t\t\tres.Core = true\n\t\t\t\t\tres.Domain = domain\n\t\t\t\t\tres.Path = path.Join(\"k8s.io\", \"api\", res.Group, res.Version)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/plugins/golang/options_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"path\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n)\n\nvar _ = Describe(\"Options\", func() {\n\tContext(\"UpdateResource\", func() {\n\t\tconst (\n\t\t\tgroup   = \"crew\"\n\t\t\tdomain  = \"test.io\"\n\t\t\tversion = \"v1\"\n\t\t\tkind    = \"FirstMate\"\n\t\t)\n\n\t\tvar (\n\t\t\tgvk resource.GVK\n\t\t\tcfg config.Config\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tgvk = resource.GVK{\n\t\t\t\tGroup:   group,\n\t\t\t\tDomain:  domain,\n\t\t\t\tVersion: version,\n\t\t\t\tKind:    kind,\n\t\t\t}\n\n\t\t\tcfg = cfgv3.New()\n\t\t\t_ = cfg.SetRepository(\"test\")\n\t\t})\n\n\t\tDescribeTable(\"should succeed\",\n\t\t\tfunc(options Options) {\n\t\t\t\tfor _, multiGroup := range []bool{false, true} {\n\t\t\t\t\tif multiGroup {\n\t\t\t\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(cfg.ClearMultiGroup()).To(Succeed())\n\t\t\t\t\t}\n\n\t\t\t\t\tres := resource.Resource{\n\t\t\t\t\t\tGVK:      gvk,\n\t\t\t\t\t\tPlural:   \"firstmates\",\n\t\t\t\t\t\tAPI:      &resource.API{},\n\t\t\t\t\t\tWebhooks: &resource.Webhooks{},\n\t\t\t\t\t}\n\n\t\t\t\t\toptions.UpdateResource(&res, cfg)\n\t\t\t\t\tExpect(res.Validate()).To(Succeed())\n\t\t\t\t\tExpect(res.GVK.IsEqualTo(gvk)).To(BeTrue())\n\t\t\t\t\tif options.Plural != \"\" {\n\t\t\t\t\t\tExpect(res.Plural).To(Equal(options.Plural))\n\t\t\t\t\t}\n\t\t\t\t\tif options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion {\n\t\t\t\t\t\tif multiGroup {\n\t\t\t\t\t\t\tExpect(res.Path).To(Equal(\n\t\t\t\t\t\t\t\tpath.Join(cfg.GetRepository(), \"api\", gvk.Group, gvk.Version)))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tExpect(res.Path).To(Equal(path.Join(cfg.GetRepository(), \"api\", gvk.Version)))\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if len(options.ExternalAPIPath) > 0 {\n\t\t\t\t\t\tExpect(res.Path).To(Equal(\"testPath\"))\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Core-resources have a path despite not having an API/Webhook but they are not tested here\n\t\t\t\t\t\tExpect(res.Path).To(Equal(\"\"))\n\t\t\t\t\t}\n\t\t\t\t\tExpect(res.API).NotTo(BeNil())\n\t\t\t\t\tif options.DoAPI {\n\t\t\t\t\t\tExpect(res.API.Namespaced).To(Equal(options.Namespaced))\n\t\t\t\t\t\tExpect(res.API.IsEmpty()).To(BeFalse())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(res.API.IsEmpty()).To(BeTrue())\n\t\t\t\t\t}\n\t\t\t\t\tExpect(res.Controller).To(Equal(options.DoController))\n\t\t\t\t\tExpect(res.Webhooks).NotTo(BeNil())\n\t\t\t\t\tif options.DoDefaulting || options.DoValidation || options.DoConversion {\n\t\t\t\t\t\tExpect(res.Webhooks.Defaulting).To(Equal(options.DoDefaulting))\n\t\t\t\t\t\tExpect(res.Webhooks.Validation).To(Equal(options.DoValidation))\n\t\t\t\t\t\tExpect(res.Webhooks.Conversion).To(Equal(options.DoConversion))\n\t\t\t\t\t\tExpect(res.Webhooks.Spoke).To(Equal(options.Spoke))\n\t\t\t\t\t\tExpect(res.Webhooks.IsEmpty()).To(BeFalse())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(res.Webhooks.IsEmpty()).To(BeTrue())\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(options.ExternalAPIPath) > 0 {\n\t\t\t\t\t\tExpect(res.External).To(BeTrue())\n\t\t\t\t\t\tExpect(res.Domain).To(Equal(\"test.io\"))\n\t\t\t\t\t}\n\n\t\t\t\t\tExpect(res.QualifiedGroup()).To(Equal(gvk.Group + \".\" + gvk.Domain))\n\t\t\t\t\tExpect(res.PackageName()).To(Equal(gvk.Group))\n\t\t\t\t\tExpect(res.ImportAlias()).To(Equal(gvk.Group + gvk.Version))\n\t\t\t\t}\n\t\t\t},\n\t\t\tEntry(\"when updating nothing\", Options{}),\n\t\t\tEntry(\"when updating the plural\", Options{Plural: \"mates\"}),\n\t\t\tEntry(\"when updating the Controller\", Options{DoController: true}),\n\t\t\tEntry(\"when updating with External API Path\", Options{ExternalAPIPath: \"testPath\", ExternalAPIDomain: \"test.io\"}),\n\t\t\tEntry(\"when updating the API with setting webhooks params\",\n\t\t\t\tOptions{DoAPI: true, DoDefaulting: true, DoValidation: true, DoConversion: true}),\n\t\t)\n\n\t\tDescribeTable(\"should use core apis\",\n\t\t\tfunc(group, qualified string) {\n\t\t\t\toptions := Options{}\n\t\t\t\tfor _, multiGroup := range []bool{false, true} {\n\t\t\t\t\tif multiGroup {\n\t\t\t\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(cfg.ClearMultiGroup()).To(Succeed())\n\t\t\t\t\t}\n\n\t\t\t\t\tres := resource.Resource{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   group,\n\t\t\t\t\t\t\tDomain:  domain,\n\t\t\t\t\t\t\tVersion: version,\n\t\t\t\t\t\t\tKind:    kind,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPlural:   \"firstmates\",\n\t\t\t\t\t\tAPI:      &resource.API{},\n\t\t\t\t\t\tWebhooks: &resource.Webhooks{},\n\t\t\t\t\t}\n\n\t\t\t\t\toptions.UpdateResource(&res, cfg)\n\t\t\t\t\tExpect(res.Validate()).To(Succeed())\n\n\t\t\t\t\tExpect(res.Path).To(Equal(path.Join(\"k8s.io\", \"api\", group, version)))\n\t\t\t\t\tExpect(res.HasAPI()).To(BeFalse())\n\t\t\t\t\tExpect(res.QualifiedGroup()).To(Equal(qualified))\n\t\t\t\t}\n\t\t\t},\n\t\t\tEntry(\"for `apps`\", \"apps\", \"apps\"),\n\t\t\tEntry(\"for `authentication`\", \"authentication\", \"authentication.k8s.io\"),\n\t\t)\n\n\t\tDescribeTable(\"should use core apis with project version 2\",\n\t\t\t// This needs a separate test because project version 2 didn't store API and therefore\n\t\t\t// the `HasAPI` method of the resource obtained with `GetResource` will always return false.\n\t\t\t// Instead, the existence of a resource in the list means the API was scaffolded.\n\t\t\tfunc(group, qualified string) {\n\t\t\t\tcfg = cfgv3.New()\n\t\t\t\t_ = cfg.SetRepository(\"test\")\n\n\t\t\t\toptions := Options{}\n\t\t\t\tfor _, multiGroup := range []bool{false, true} {\n\t\t\t\t\tif multiGroup {\n\t\t\t\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tExpect(cfg.ClearMultiGroup()).To(Succeed())\n\t\t\t\t\t}\n\n\t\t\t\t\tres := resource.Resource{\n\t\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\t\tGroup:   group,\n\t\t\t\t\t\t\tDomain:  domain,\n\t\t\t\t\t\t\tVersion: version,\n\t\t\t\t\t\t\tKind:    kind,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPlural:   \"firstmates\",\n\t\t\t\t\t\tAPI:      &resource.API{},\n\t\t\t\t\t\tWebhooks: &resource.Webhooks{},\n\t\t\t\t\t}\n\n\t\t\t\t\toptions.UpdateResource(&res, cfg)\n\t\t\t\t\tExpect(res.Validate()).To(Succeed())\n\n\t\t\t\t\tExpect(res.Path).To(Equal(path.Join(\"k8s.io\", \"api\", group, version)))\n\t\t\t\t\tExpect(res.HasAPI()).To(BeFalse())\n\t\t\t\t\tExpect(res.QualifiedGroup()).To(Equal(qualified))\n\t\t\t\t}\n\t\t\t},\n\t\t\tEntry(\"for `apps`\", \"apps\", \"apps\"),\n\t\t\tEntry(\"for `authentication`\", \"authentication\", \"authentication.k8s.io\"),\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/repository.go",
    "content": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\n\t\"golang.org/x/tools/go/packages\"\n)\n\n// module and goMod arg just enough of the output of `go mod edit -json` for our purposes\ntype goMod struct {\n\tModule module\n}\ntype module struct {\n\tPath string\n}\n\n// findGoModulePath finds the path of the current module, if present.\nfunc findGoModulePath() (string, error) {\n\tcmd := exec.Command(\"go\", \"mod\", \"edit\", \"-json\")\n\tcmd.Env = append(cmd.Env, os.Environ()...)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\tif errors.As(err, &exitErr) {\n\t\t\terr = fmt.Errorf(\"%s\", string(exitErr.Stderr))\n\t\t}\n\t\treturn \"\", err\n\t}\n\tmod := goMod{}\n\tif err = json.Unmarshal(out, &mod); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to unmarshal go.mod: %w\", err)\n\t}\n\treturn mod.Module.Path, nil\n}\n\n// FindCurrentRepo attempts to determine the current repository\n// though a combination of go/packages and `go mod` commands/tricks.\nfunc FindCurrentRepo() (string, error) {\n\t// easiest case: existing go module\n\tpath, err := findGoModulePath()\n\tif err == nil {\n\t\treturn path, nil\n\t}\n\n\t// next, check if we've got a package in the current directory\n\tpkgCfg := &packages.Config{\n\t\tMode: packages.NeedName, // name gives us path as well\n\t}\n\tpkgs, err := packages.Load(pkgCfg, \".\")\n\t// NB(directxman12): when go modules are off and we're outside GOPATH and\n\t// we don't otherwise have a good guess packages.Load will fabricate a path\n\t// that consists of `_/absolute/path/to/current/directory`.  We shouldn't\n\t// use that when it happens.\n\tif err == nil && len(pkgs) > 0 && len(pkgs[0].PkgPath) > 0 && pkgs[0].PkgPath[0] != '_' {\n\t\treturn pkgs[0].PkgPath, nil\n\t}\n\n\t// otherwise, try to get `go mod init` to guess for us -- it's pretty good\n\tcmd := exec.Command(\"go\", \"mod\", \"init\")\n\tcmd.Env = append(cmd.Env, os.Environ()...)\n\tif _, err := cmd.Output(); err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\tif errors.As(err, &exitErr) {\n\t\t\terr = fmt.Errorf(\"%s\", string(exitErr.Stderr))\n\t\t}\n\t\t// give up, let the user figure it out\n\t\treturn \"\", fmt.Errorf(\"could not determine repository path from module data, \"+\n\t\t\t\"package data, or by initializing a module: %w\", err)\n\t}\n\t//nolint:errcheck\n\tdefer os.Remove(\"go.mod\") // clean up after ourselves\n\treturn findGoModulePath()\n}\n"
  },
  {
    "path": "pkg/plugins/golang/repository_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"os\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"golang:repository\", func() {\n\tvar (\n\t\ttmpDir string\n\t\toldDir string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"repo-test\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\toldDir, err = os.Getwd()\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(os.Chdir(tmpDir)).To(Succeed())\n\t})\n\n\tAfterEach(func() {\n\t\tExpect(os.Chdir(oldDir)).To(Succeed())\n\t\tExpect(os.RemoveAll(tmpDir)).To(Succeed())\n\t})\n\n\tWhen(\"go.mod exists\", func() {\n\t\tBeforeEach(func() {\n\t\t\t// Simulate `go mod edit -json` output by writing a go.mod file and using go commands\n\t\t\tExpect(os.WriteFile(\"go.mod\", []byte(\"module github.com/example/repo\\n\"), 0o644)).To(Succeed())\n\t\t})\n\n\t\tIt(\"findGoModulePath returns the module path\", func() {\n\t\t\tpath, err := findGoModulePath()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(path).To(Equal(\"github.com/example/repo\"))\n\t\t})\n\n\t\tIt(\"FindCurrentRepo returns the module path\", func() {\n\t\t\tpath, err := FindCurrentRepo()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(path).To(Equal(\"github.com/example/repo\"))\n\t\t})\n\t})\n\n\tWhen(\"go.mod does not exist\", func() {\n\t\tIt(\"findGoModulePath returns error\", func() {\n\t\t\tgot, err := findGoModulePath()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(got).To(Equal(\"\"))\n\t\t})\n\n\t\tIt(\"FindCurrentRepo tries to init a module and returns the path or a helpful error\", func() {\n\t\t\tpath, err := FindCurrentRepo()\n\t\t\tif err != nil {\n\t\t\t\tExpect(path).To(Equal(\"\"))\n\t\t\t\tExpect(err.Error()).To(ContainSubstring(\"could not determine repository path\"))\n\t\t\t} else {\n\t\t\t\tExpect(path).NotTo(BeEmpty())\n\t\t\t}\n\t\t})\n\t})\n\n\tWhen(\"go mod command fails with exec.ExitError\", func() {\n\t\tvar origPath string\n\n\t\tBeforeEach(func() {\n\t\t\t// Move go binary out of PATH to force exec error\n\t\t\torigPath = os.Getenv(\"PATH\")\n\t\t\t// Set PATH to empty so \"go\" cannot be found\n\t\t\tExpect(os.Setenv(\"PATH\", \"\")).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tExpect(os.Setenv(\"PATH\", origPath)).To(Succeed())\n\t\t})\n\n\t\tIt(\"findGoModulePath returns error with stderr message\", func() {\n\t\t\tgot, err := findGoModulePath()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).NotTo(BeEmpty())\n\t\t\tExpect(got).To(Equal(\"\"))\n\t\t})\n\n\t\tIt(\"FindCurrentRepo returns error with stderr message\", func() {\n\t\t\tgot, err := FindCurrentRepo()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"could not determine repository path\"))\n\t\t\tExpect(got).To(Equal(\"\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/suite_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage golang\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestGoPlugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Go Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n)\n\n// DefaultMainPath is default file path of main.go\nconst DefaultMainPath = \"cmd/main.go\"\n\nvar _ plugin.CreateAPISubcommand = &createAPISubcommand{}\n\ntype createAPISubcommand struct {\n\tconfig config.Config\n\n\toptions *goPlugin.Options\n\n\tresource *resource.Resource\n\n\t// Check if we have to scaffold resource and/or controller\n\tresourceFlag   *pflag.Flag\n\tcontrollerFlag *pflag.Flag\n\n\t// force indicates that the resource should be created even if it already exists\n\tforce bool\n\n\t// runMake indicates whether to run make or not after scaffolding APIs\n\trunMake bool\n}\n\nfunc (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.\n\nIf information about whether the resource and controller should be scaffolded\nwas not explicitly provided, it will prompt the user if they should be.\n\nAfter the scaffold is written, the dependencies will be updated and\nmake generate will be run.\n`\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate\n  %[1]s create api --group ship --version v1beta1 --kind Frigate\n\n  # Edit the API Scheme\n\n  nano api/v1beta1/frigate_types.go\n\n  # Edit the Controller\n  nano internal/controller/frigate/frigate_controller.go\n\n  # Edit the Controller Test\n  nano internal/controller/frigate/frigate_controller_test.go\n\n  # Generate the manifests\n  make manifests\n\n  # Install CRDs into the Kubernetes cluster using kubectl apply\n  make install\n\n  # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config\n  make run\n`, cliMeta.CommandName)\n}\n\nfunc (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.runMake, \"make\", true, \"if true, run `make generate` after generating files\")\n\n\tfs.BoolVar(&p.force, \"force\", false,\n\t\t\"attempt to create resource even if it already exists\")\n\n\tp.options = &goPlugin.Options{}\n\n\tfs.StringVar(&p.options.Plural, \"plural\", \"\", \"resource irregular plural form\")\n\n\tfs.BoolVar(&p.options.DoAPI, \"resource\", true,\n\t\t\"if set, generate the resource without prompting the user\")\n\tp.resourceFlag = fs.Lookup(\"resource\")\n\tfs.BoolVar(&p.options.Namespaced, \"namespaced\", true, \"resource is namespaced\")\n\n\tfs.BoolVar(&p.options.DoController, \"controller\", true,\n\t\t\"if set, generate the controller without prompting the user\")\n\tp.controllerFlag = fs.Lookup(\"controller\")\n\n\tfs.StringVar(&p.options.ExternalAPIPath, \"external-api-path\", \"\",\n\t\t\"Specify the Go package import path for the external API. This is used to scaffold controllers for resources \"+\n\t\t\t\"defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).\")\n\n\tfs.StringVar(&p.options.ExternalAPIDomain, \"external-api-domain\", \"\",\n\t\t\"Specify the domain name for the external API. This domain is used to generate accurate RBAC \"+\n\t\t\t\"markers and permissions for the external resources (e.g., cert-manager.io).\")\n\n\tfs.StringVar(&p.options.ExternalAPIModule, \"external-api-module\", \"\",\n\t\t\"external API module with optional version (e.g., github.com/cert-manager/cert-manager@v1.18.2)\")\n}\n\nfunc (p *createAPISubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) InjectResource(res *resource.Resource) error {\n\tp.resource = res\n\n\treader := bufio.NewReader(os.Stdin)\n\tif !p.resourceFlag.Changed {\n\t\tlog.Info(\"Create Resource [y/n]\")\n\t\tp.options.DoAPI = util.YesNo(reader)\n\t}\n\tif !p.controllerFlag.Changed {\n\t\tlog.Info(\"Create Controller [y/n]\")\n\t\tp.options.DoController = util.YesNo(reader)\n\t}\n\n\t// Ensure that external API options cannot be used when creating an API in the project.\n\tif p.options.DoAPI {\n\t\tif len(p.options.ExternalAPIPath) != 0 || len(p.options.ExternalAPIDomain) != 0 ||\n\t\t\tlen(p.options.ExternalAPIModule) != 0 {\n\t\t\treturn errors.New(\"cannot use '--external-api-path', '--external-api-domain', or '--external-api-module' \" +\n\t\t\t\t\"when creating an API in the project with '--resource=true'. \" +\n\t\t\t\t\"Use '--resource=false' when referencing an external API\")\n\t\t}\n\t}\n\n\t// Validate that --external-api-module requires --external-api-path\n\tif len(p.options.ExternalAPIModule) != 0 && len(p.options.ExternalAPIPath) == 0 {\n\t\treturn errors.New(\"'--external-api-module' requires '--external-api-path' to be specified\")\n\t}\n\n\tp.options.UpdateResource(p.resource, p.config)\n\n\tif err := p.resource.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"error validating resource: %w\", err)\n\t}\n\n\t// In case we want to scaffold a resource API we need to do some checks\n\tif p.options.DoAPI {\n\t\t// Check that resource doesn't have the API scaffolded or flag force was set\n\t\tif r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {\n\t\t\treturn errors.New(\"API resource already exists\")\n\t\t}\n\n\t\t// Check that the provided group can be added to the project\n\t\tif !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {\n\t\t\treturn fmt.Errorf(\"multiple groups are not allowed by default, \" +\n\t\t\t\t\"to enable multi-group visit https://kubebuilder.io/migration/multi-group.html\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {\n\t// check if main.go is present in the root directory\n\tif _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"%s file should present in the root directory\", DefaultMainPath)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding API: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createAPISubcommand) PostScaffold() error {\n\t// If external API with module specified, add it using go get\n\tif p.resource.IsExternal() && p.resource.Module != \"\" {\n\t\tlog.Info(\"Adding external API dependency\", \"module\", p.resource.Module)\n\t\t// Use go get to add the dependency cleanly as a direct requirement\n\t\terr := util.RunCmd(\"Add external API dependency\", \"go\", \"get\", p.resource.Module)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error adding external API dependency: %w\", err)\n\t\t}\n\t}\n\n\terr := util.RunCmd(\"Update dependencies\", \"go\", \"mod\", \"tidy\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating go dependencies: %w\", err)\n\t}\n\tif p.runMake && p.resource.HasAPI() {\n\t\terr = util.RunCmd(\"Running make\", \"make\", \"generate\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error running make generate: %w\", err)\n\t\t}\n\t\tfmt.Print(\"Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\\n$ make manifests\\n\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/api_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n)\n\nvar _ = Describe(\"createAPISubcommand\", func() {\n\tvar (\n\t\tsubCmd *createAPISubcommand\n\t\tcfg    config.Config\n\t\tres    *resource.Resource\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &createAPISubcommand{}\n\t\tcfg = cfgv3.New()\n\t\t_ = cfg.SetRepository(\"github.com/example/test\")\n\n\t\tsubCmd.options = &goPlugin.Options{}\n\t\tsubCmd.resourceFlag = &pflag.Flag{Changed: true}\n\t\tsubCmd.controllerFlag = &pflag.Flag{Changed: true}\n\n\t\tres = &resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"crew\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Captain\",\n\t\t\t},\n\t\t\tPlural:   \"captains\",\n\t\t\tAPI:      &resource.API{},\n\t\t\tWebhooks: &resource.Webhooks{},\n\t\t}\n\n\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t})\n\n\tIt(\"should reject external API options when creating API in project\", func() {\n\t\tsubCmd.options.DoAPI = true\n\t\tsubCmd.options.ExternalAPIPath = \"github.com/external/api\"\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"cannot use '--external-api-path'\"))\n\t})\n\n\tIt(\"should require external-api-path when using external-api-module\", func() {\n\t\tsubCmd.options.DoAPI = false\n\t\tsubCmd.options.ExternalAPIModule = \"github.com/external/api@v1.0.0\"\n\t\tsubCmd.options.ExternalAPIPath = \"\"\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"requires '--external-api-path'\"))\n\t})\n\n\tIt(\"should prevent duplicate API without force flag\", func() {\n\t\tsubCmd.options.DoAPI = true\n\t\tsubCmd.options.DoController = true\n\n\t\tresWithAPI := *res\n\t\tresWithAPI.API = &resource.API{CRDVersion: \"v1\"}\n\t\tExpect(cfg.AddResource(resWithAPI)).To(Succeed())\n\n\t\tsubCmd.force = false\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"API resource already exists\"))\n\t})\n\n\tIt(\"should allow duplicate API with force flag\", func() {\n\t\tsubCmd.options.DoAPI = true\n\t\tsubCmd.options.DoController = true\n\n\t\tresWithAPI := *res\n\t\tresWithAPI.API = &resource.API{CRDVersion: \"v1\"}\n\t\tExpect(cfg.AddResource(resWithAPI)).To(Succeed())\n\n\t\tsubCmd.force = true\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tIt(\"should prevent multiple groups in single-group project\", func() {\n\t\tsubCmd.options.DoAPI = true\n\t\tsubCmd.options.DoController = true\n\n\t\tfirstRes := resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"ship\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Frigate\",\n\t\t\t},\n\t\t\tPlural: \"frigates\",\n\t\t\tAPI:    &resource.API{CRDVersion: \"v1\"},\n\t\t}\n\t\tExpect(cfg.AddResource(firstRes)).To(Succeed())\n\n\t\tres.Group = \"crew\"\n\t\tres.Plural = \"captains\"\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"multiple groups are not allowed\"))\n\t})\n\n\tIt(\"should allow multiple groups when multigroup is enabled\", func() {\n\t\tsubCmd.options.DoAPI = true\n\t\tsubCmd.options.DoController = true\n\n\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\n\t\tfirstRes := resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"ship\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Frigate\",\n\t\t\t},\n\t\t\tPlural: \"frigates\",\n\t\t\tAPI:    &resource.API{CRDVersion: \"v1\"},\n\t\t}\n\t\tExpect(cfg.AddResource(firstRes)).To(Succeed())\n\n\t\tres.Group = \"crew\"\n\n\t\tExpect(subCmd.InjectResource(res)).To(Succeed())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/edit.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tconfig config.Config\n\n\tmultigroup bool\n\tnamespaced bool\n\tforce      bool\n\n\t// fs stores the FlagSet to check if flags were explicitly set\n\tfs *pflag.FlagSet\n}\n\nfunc (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = `Edit project configuration to enable or disable layout settings.\n\nMultigroup (--multigroup):\n  Enable or disable multi-group layout.\n  Changes API structure: api/<version>/ becomes api/<group>/<version>/\n  Automatic: Updates PROJECT file, future APIs use new structure\n  Manual: Move existing API files, update import paths in controllers\n  More info: https://book.kubebuilder.io/migration/multi-group.html\n\nNamespaced (--namespaced):\n  Enable or disable namespace-scoped deployment.\n  Manager watches one or more specific namespaces vs all namespaces.\n  Namespaces to watch are configured via WATCH_NAMESPACE environment variable.\n  Automatic: Updates PROJECT file, scaffolds Role/RoleBinding, uses --force to regenerate manager.yaml\n  Manual: Add namespace= to RBAC markers in existing controllers, update cmd/main.go, run 'make manifests'\n  More info: https://book.kubebuilder.io/migration/namespace-scoped.html \n  \n  WARNING - Webhooks and Namespace-Scoped Mode:\n  Webhooks remain cluster-scoped even in namespace-scoped mode.\n  The manager cache is restricted to WATCH_NAMESPACE, but webhooks receive requests\n  from ALL namespaces. You must configure namespaceSelector or objectSelector to align\n  webhook scope with the cache.\n\nForce (--force):\n  Overwrite existing scaffolded files to apply configuration changes.\n  Example: With --namespaced, regenerates config/manager/manager.yaml to add WATCH_NAMESPACE env var.\n  Warning: This overwrites default scaffold files; manual changes in those files may be lost.\n\nNote: To add optional plugins after initialization, use 'kubebuilder edit --plugins <plugin-name>'.\n      Run 'kubebuilder edit --plugins --help' to see available plugins.\n`\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Enable multigroup layout\n  %[1]s edit --multigroup\n\n  # Enable namespace-scoped permissions\n  %[1]s edit --namespaced\n\n  # Enable with automatic file regeneration\n  %[1]s edit --namespaced --force\n\n  # Disable multigroup layout\n  %[1]s edit --multigroup=false\n\n  # Enable/disable multiple settings\n  %[1]s edit --multigroup --namespaced --force\n`, cliMeta.CommandName)\n}\n\nfunc (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tp.fs = fs\n\tfs.BoolVar(&p.multigroup, \"multigroup\", false, \"enable or disable multigroup layout\")\n\tfs.BoolVar(&p.namespaced, \"namespaced\", false, \"enable or disable namespace-scoped deployment\")\n\tfs.BoolVar(&p.force, \"force\", false, \"overwrite scaffolded files to apply changes (manual edits may be lost)\")\n}\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\treturn nil\n}\n\nfunc (p *editSubcommand) PreScaffold(machinery.Filesystem) error {\n\t// If flags were not explicitly set, preserve existing PROJECT file values\n\t// This prevents one flag from clearing another when using default values\n\tif !p.fs.Changed(\"multigroup\") {\n\t\tp.multigroup = p.config.IsMultiGroup()\n\t}\n\tif !p.fs.Changed(\"namespaced\") {\n\t\tp.namespaced = p.config.IsNamespaced()\n\t}\n\n\treturn nil\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup, p.namespaced, p.force)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to edit scaffold: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/edit_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"editSubcommand\", func() {\n\tvar (\n\t\tsubCmd *editSubcommand\n\t\tcfg    config.Config\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &editSubcommand{}\n\t\tcfg = cfgv3.New()\n\t})\n\n\tIt(\"should inject config successfully\", func() {\n\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t\tExpect(subCmd.config).To(Equal(cfg))\n\t})\n\n\tContext(\"PreScaffold\", func() {\n\t\tvar (\n\t\t\tfs     *pflag.FlagSet\n\t\t\tmockFS machinery.Filesystem\n\t\t)\n\n\t\tBeforeEach(func() {\n\t\t\tfs = pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\t\t\tsubCmd.BindFlags(fs)\n\t\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t\t\tmockFS = machinery.Filesystem{FS: afero.NewMemMapFs()}\n\t\t})\n\n\t\tIt(\"should preserve existing multigroup setting when only namespaced flag is set\", func() {\n\t\t\t// Set multigroup in PROJECT file\n\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\tExpect(cfg.IsMultiGroup()).To(BeTrue())\n\n\t\t\t// Only set namespaced flag (multigroup not set, so it defaults to false)\n\t\t\tExpect(fs.Set(\"namespaced\", \"true\")).To(Succeed())\n\n\t\t\t// PreScaffold should preserve the existing multigroup value\n\t\t\tExpect(subCmd.PreScaffold(mockFS)).To(Succeed())\n\n\t\t\t// Both should be true\n\t\t\tExpect(subCmd.multigroup).To(BeTrue(), \"multigroup should be preserved from PROJECT file\")\n\t\t\tExpect(subCmd.namespaced).To(BeTrue(), \"namespaced should be set from flag\")\n\t\t})\n\n\t\tIt(\"should preserve existing namespaced setting when only multigroup flag is set\", func() {\n\t\t\t// Set namespaced in PROJECT file\n\t\t\tExpect(cfg.SetNamespaced()).To(Succeed())\n\t\t\tExpect(cfg.IsNamespaced()).To(BeTrue())\n\n\t\t\t// Only set multigroup flag (namespaced not set, so it defaults to false)\n\t\t\tExpect(fs.Set(\"multigroup\", \"true\")).To(Succeed())\n\n\t\t\t// PreScaffold should preserve the existing namespaced value\n\t\t\tExpect(subCmd.PreScaffold(mockFS)).To(Succeed())\n\n\t\t\t// Both should be true\n\t\t\tExpect(subCmd.multigroup).To(BeTrue(), \"multigroup should be set from flag\")\n\t\t\tExpect(subCmd.namespaced).To(BeTrue(), \"namespaced should be preserved from PROJECT file\")\n\t\t})\n\n\t\tIt(\"should allow explicitly disabling a flag\", func() {\n\t\t\t// Set both in PROJECT file\n\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\tExpect(cfg.SetNamespaced()).To(Succeed())\n\n\t\t\t// Explicitly disable multigroup\n\t\t\tExpect(fs.Set(\"multigroup\", \"false\")).To(Succeed())\n\n\t\t\t// PreScaffold should respect the explicit false\n\t\t\tExpect(subCmd.PreScaffold(mockFS)).To(Succeed())\n\n\t\t\t// multigroup should be false (explicitly set), namespaced should be true (from PROJECT)\n\t\t\tExpect(subCmd.multigroup).To(BeFalse(), \"multigroup should be explicitly disabled\")\n\t\t\tExpect(subCmd.namespaced).To(BeTrue(), \"namespaced should be preserved from PROJECT file\")\n\t\t})\n\n\t\tIt(\"should use flag values when both flags are explicitly set\", func() {\n\t\t\t// Set different values in PROJECT file\n\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\tExpect(cfg.ClearNamespaced()).To(Succeed())\n\n\t\t\t// Explicitly set both flags to opposite values\n\t\t\tExpect(fs.Set(\"multigroup\", \"false\")).To(Succeed())\n\t\t\tExpect(fs.Set(\"namespaced\", \"true\")).To(Succeed())\n\n\t\t\t// PreScaffold should use the explicit flag values\n\t\t\tExpect(subCmd.PreScaffold(mockFS)).To(Succeed())\n\n\t\t\tExpect(subCmd.multigroup).To(BeFalse(), \"multigroup should use explicit flag value\")\n\t\t\tExpect(subCmd.namespaced).To(BeTrue(), \"namespaced should use explicit flag value\")\n\t\t})\n\n\t\tIt(\"should preserve PROJECT file values when no flags are set\", func() {\n\t\t\t// Set values in PROJECT file\n\t\t\tExpect(cfg.SetMultiGroup()).To(Succeed())\n\t\t\tExpect(cfg.SetNamespaced()).To(Succeed())\n\n\t\t\t// Don't set any flags\n\n\t\t\t// PreScaffold should preserve both values from PROJECT file\n\t\t\tExpect(subCmd.PreScaffold(mockFS)).To(Succeed())\n\n\t\t\tExpect(subCmd.multigroup).To(BeTrue(), \"multigroup should be preserved from PROJECT file\")\n\t\t\tExpect(subCmd.namespaced).To(BeTrue(), \"namespaced should be preserved from PROJECT file\")\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n)\n\n// Variables and function to check Go version requirements.\nvar (\n\tgoVerMin = golang.MustParse(\"go1.23.0\")\n\tgoVerMax = golang.MustParse(\"go2.0alpha1\")\n)\n\nvar _ plugin.InitSubcommand = &initSubcommand{}\n\ntype initSubcommand struct {\n\tconfig config.Config\n\t// For help text.\n\tcommandName string\n\n\t// boilerplate options\n\tlicense string\n\towner   string\n\n\t// go config options\n\trepo string\n\n\t// flags\n\tfetchDeps          bool\n\tskipGoVersionCheck bool\n\tmultigroup         bool\n\tnamespaced         bool\n}\n\nfunc (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tp.commandName = cliMeta.CommandName\n\n\tsubcmdMeta.Description = `Initialize a new project including the following files:\n  - a \"go.mod\" with project dependencies\n  - a \"PROJECT\" file that stores project configuration\n  - a \"Makefile\" with several useful make targets for the project\n  - several YAML files for project deployment under the \"config\" directory\n  - a \"cmd/main.go\" file that creates the manager that will run the project controllers\n\nRequired flags:\n  --domain: Domain for your APIs (e.g., example.org creates crew.example.org for API groups)\n\nConfiguration flags:\n  --repo: Go module path (e.g., github.com/user/repo); auto-detected if not provided\n  --owner: Owner name for copyright license headers\n  --license: License to use (apache2 or none, default: apache2)\n\nPlugin flags:\n  --plugins: Comma-separated list of plugins to use (default: go/v4)\n             Plugins scaffold files during init and are saved to the PROJECT layout\n             Future operations (i.e. create api, create webhook) call all plugins in the chain\n             Run 'kubebuilder init --plugins --help' to see available plugins\n\nLayout flags:\n  --multigroup: Enable multigroup layout to organize APIs by group\n                Scaffolds APIs in api/<group>/<version>/ instead of api/<version>/\n                Useful when managing multiple API groups (e.g., batch, apps, crew)\n  --namespaced: Enable namespace-scoped deployment instead of cluster-scoped\n                Manager watches one or more specific namespaces instead of all namespaces\n                Namespaces to watch are configured via WATCH_NAMESPACE environment variable\n                Uses Role/RoleBinding instead of ClusterRole/ClusterRoleBinding\n                Suitable for multi-tenant environments or limited scope deployments\n\nNote: Layout settings can be changed later with 'kubebuilder edit'.\n`\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Initialize a new project\n  %[1]s init --domain example.org\n\n  # Initialize with multigroup layout\n  %[1]s init --domain example.org --multigroup\n\n  # Initialize with namespace-scoped deployment\n  %[1]s init --domain example.org --namespaced\n\n  # Initialize with optional plugins\n  %[1]s init --plugins go/v4,autoupdate/v1-alpha --domain example.org\n  %[1]s init --plugins go/v4,helm/v2-alpha --domain example.org\n\n  # Initialize with custom settings\n  %[1]s init --domain example.org --owner \"Your Name\" --license apache2\n\n  # Initialize with all options combined\n  %[1]s init --plugins go/v4,autoupdate/v1-alpha --domain example.org --multigroup --namespaced\n`, cliMeta.CommandName)\n}\n\nfunc (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.skipGoVersionCheck, \"skip-go-version-check\",\n\t\tfalse, \"skip Go version check\")\n\n\t// dependency args\n\tfs.BoolVar(&p.fetchDeps, \"fetch-deps\", true, \"download dependencies after scaffolding\")\n\n\t// boilerplate args\n\tfs.StringVar(&p.license, \"license\", \"apache2\",\n\t\t\"license header to use (apache2 or none)\")\n\tfs.StringVar(&p.owner, \"owner\", \"\", \"copyright owner for license headers\")\n\n\t// project args\n\tfs.StringVar(&p.repo, \"repo\", \"\", \"Go module name (e.g., github.com/user/repo); \"+\n\t\t\"auto-detected from current directory if not provided\")\n\tfs.BoolVar(&p.multigroup, \"multigroup\", false,\n\t\t\"enable multigroup layout (organize APIs by group)\")\n\tfs.BoolVar(&p.namespaced, \"namespaced\", false,\n\t\t\"enable namespace-scoped deployment (default: cluster-scoped)\")\n}\n\nfunc (p *initSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\n\t// Try to guess repository if flag is not set.\n\tif p.repo == \"\" {\n\t\trepoPath, err := golang.FindCurrentRepo()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error finding current repository: %w\", err)\n\t\t}\n\t\tp.repo = repoPath\n\t}\n\n\tif err := p.config.SetRepository(p.repo); err != nil {\n\t\treturn fmt.Errorf(\"error setting repository: %w\", err)\n\t}\n\n\tif p.multigroup {\n\t\tif err := p.config.SetMultiGroup(); err != nil {\n\t\t\treturn fmt.Errorf(\"error setting multigroup: %w\", err)\n\t\t}\n\t}\n\n\tif p.namespaced {\n\t\tif err := p.config.SetNamespaced(); err != nil {\n\t\t\treturn fmt.Errorf(\"error setting namespaced: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *initSubcommand) PreScaffold(machinery.Filesystem) error {\n\t// Ensure Go version is in the allowed range if check not turned off.\n\tif !p.skipGoVersionCheck {\n\t\tif err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {\n\t\t\treturn fmt.Errorf(\"error validating go version: %w\", err)\n\t\t}\n\t}\n\n\t// Check if the current directory has no files or directories which does not allow to init the project\n\treturn checkDir()\n}\n\nfunc (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner, p.commandName)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding init plugin: %w\", err)\n\t}\n\n\tif !p.fetchDeps {\n\t\tlog.Info(\"skipping fetching dependencies\")\n\t\treturn nil\n\t}\n\n\t// Ensure that we are pinning controller-runtime version\n\t// xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997\n\terr := util.RunCmd(\"Get controller runtime\", \"go\", \"get\",\n\t\t\"sigs.k8s.io/controller-runtime@\"+scaffolds.ControllerRuntimeVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting controller-runtime version: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *initSubcommand) PostScaffold() error {\n\terr := util.RunCmd(\"Update dependencies\", \"go\", \"mod\", \"tidy\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating go dependencies: %w\", err)\n\t}\n\n\tfmt.Printf(\"Next: define a resource with:\\n$ %s create api\\n\", p.commandName)\n\treturn nil\n}\n\n// checkDir checks the target directory before scaffolding:\n// 1. Returns error if key kubebuilder files already exist (prevents re-initialization)\n// 2. Warns if directory is not empty (but allows scaffolding to continue)\nfunc checkDir() error {\n\t// Files scaffolded by 'kubebuilder init' that indicate the directory is already initialized.\n\t// Blocking these prevents accidental re-initialization and file conflicts.\n\t// Note: go.mod and go.sum are NOT blocked because:\n\t//   - They may exist in pre-existing Go projects\n\t//   - Kubebuilder will overwrite them (machinery.OverwriteFile)\n\t//   - Testdata generation creates go.mod before running init\n\tscaffoldedFiles := []string{\n\t\t\"PROJECT\",                       // Kubebuilder project config (key indicator)\n\t\t\"Makefile\",                      // Build automation\n\t\tfilepath.Join(\"cmd\", \"main.go\"), // Controller manager entry point\n\t}\n\n\t// Check for existing scaffolded files\n\tfor _, file := range scaffoldedFiles {\n\t\tif _, err := os.Stat(file); err == nil {\n\t\t\treturn fmt.Errorf(\"target directory is already initialized. \"+\n\t\t\t\t\"Found existing kubebuilder file %q. \"+\n\t\t\t\t\"Please run this command in a new directory or remove existing scaffolded files\", file)\n\t\t}\n\t}\n\n\t// Check if directory has any other files (warn only)\n\t// Note: We ignore certain files that are expected or safely overwritten:\n\t//   - go.mod and go.sum: Users may run `go mod init` before `kubebuilder init`\n\t//   - .gitignore and .dockerignore: Safely overwritten by kubebuilder\n\t//   - Other dot directories (.git, .vscode, .idea): Not scaffolded by kubebuilder\n\t// However, we DO check .github directory since kubebuilder scaffolds workflows there\n\tvar hasFiles bool\n\terr := filepath.Walk(\".\",\n\t\tfunc(path string, info os.FileInfo, err error) error {\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error walking path %q: %w\", path, err)\n\t\t\t}\n\t\t\t// Skip the current directory itself\n\t\t\tif path == \".\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// Skip dot directories EXCEPT .github (which contains scaffolded workflows)\n\t\t\tif info.IsDir() && strings.HasPrefix(info.Name(), \".\") {\n\t\t\t\tif info.Name() != \".github\" {\n\t\t\t\t\treturn filepath.SkipDir\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Skip files that are expected or safely overwritten\n\t\t\tignoredFiles := []string{\"go.mod\", \"go.sum\"}\n\t\t\tif slices.Contains(ignoredFiles, info.Name()) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\t// Track if any other files/directories exist\n\t\t\thasFiles = true\n\t\t\treturn nil\n\t\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error walking directory: %w\", err)\n\t}\n\n\t// Warn if directory is not empty (but don't block)\n\tif hasFiles {\n\t\tlog.Warn(\"The target directory is not empty. \" +\n\t\t\t\"Scaffolding may overwrite existing files or cause conflicts. \" +\n\t\t\t\"It is recommended to initialize in an empty directory.\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/init_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nconst testRepo = \"github.com/example/test\"\n\nvar _ = Describe(\"initSubcommand\", func() {\n\tvar (\n\t\tsubCmd *initSubcommand\n\t\tcfg    config.Config\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &initSubcommand{}\n\t\tcfg = cfgv3.New()\n\t})\n\n\tContext(\"InjectConfig\", func() {\n\t\tIt(\"should set repository when provided\", func() {\n\t\t\tsubCmd.repo = testRepo\n\t\t\terr := subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg.GetRepository()).To(Equal(testRepo))\n\t\t})\n\n\t\tIt(\"should fail when repository cannot be detected\", func() {\n\t\t\toriginalDir, err := os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.Chdir(originalDir) }()\n\n\t\t\ttmpDir, err := os.MkdirTemp(\"\", \"test-init\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdefer func() { _ = os.RemoveAll(tmpDir) }()\n\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tsubCmd.repo = \"\"\n\t\t\terr = subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should set multigroup when flag is enabled\", func() {\n\t\t\tsubCmd.repo = testRepo\n\t\t\tsubCmd.multigroup = true\n\t\t\terr := subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg.IsMultiGroup()).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should not set multigroup when flag is disabled\", func() {\n\t\t\tsubCmd.repo = testRepo\n\t\t\tsubCmd.multigroup = false\n\t\t\terr := subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg.IsMultiGroup()).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should set namespaced when flag is enabled\", func() {\n\t\t\tsubCmd.repo = testRepo\n\t\t\tsubCmd.namespaced = true\n\t\t\terr := subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg.IsNamespaced()).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should set both multigroup and namespaced when both flags are enabled\", func() {\n\t\t\tsubCmd.repo = testRepo\n\t\t\tsubCmd.multigroup = true\n\t\t\tsubCmd.namespaced = true\n\t\t\terr := subCmd.InjectConfig(cfg)\n\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(cfg.IsMultiGroup()).To(BeTrue())\n\t\t\tExpect(cfg.IsNamespaced()).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"checkDir validation\", func() {\n\t\tvar tmpDir string\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\ttmpDir, err = os.MkdirTemp(\"\", \"test-checkdir\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\toriginalDir, err := os.Getwd()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tDeferCleanup(func() {\n\t\t\t\t_ = os.Chdir(originalDir)\n\t\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t\t})\n\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should pass for empty directory\", func() {\n\t\t\tExpect(checkDir()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should pass when only go.mod exists\", func() {\n\t\t\terr := os.WriteFile(\"go.mod\", []byte(\"module test\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(checkDir()).To(Succeed())\n\t\t})\n\n\t\tIt(\"should fail when PROJECT already exists\", func() {\n\t\t\terr := os.WriteFile(\"PROJECT\", []byte(\"version: 3\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = checkDir()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"already initialized\"))\n\t\t})\n\n\t\tIt(\"should fail when Makefile exists\", func() {\n\t\t\terr := os.WriteFile(\"Makefile\", []byte(\"all:\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = checkDir()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"already initialized\"))\n\t\t})\n\n\t\tIt(\"should fail when cmd/main.go exists\", func() {\n\t\t\terr := os.MkdirAll(\"cmd\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = os.WriteFile(filepath.Join(\"cmd\", \"main.go\"), []byte(\"package main\"), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = checkDir()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"already initialized\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/plugin.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n)\n\nconst pluginName = \"base.\" + golang.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 4, Stage: stage.Stable}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\nvar _ plugin.Full = Plugin{}\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\tinitSubcommand\n\tcreateAPISubcommand\n\tcreateWebhookSubcommand\n\teditSubcommand\n}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding\nfunc (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }\n\n// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis\nfunc (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand }\n\n// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks\nfunc (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand {\n\treturn &p.createWebhookSubcommand\n}\n\n// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Default scaffold (go/v4 + kustomize/v2)\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/plugin_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nvar _ = Describe(\"Plugin\", func() {\n\tvar p Plugin\n\n\tIt(\"should have correct version and support v3 projects\", func() {\n\t\tExpect(p.Version().Number).To(Equal(4))\n\t\tExpect(p.SupportedProjectVersions()).To(ContainElement(cfgv3.Version))\n\t})\n\n\tIt(\"should not be deprecated\", func() {\n\t\tExpect(p.DeprecationWarning()).To(BeEmpty())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/api.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack\"\n)\n\nvar _ plugins.Scaffolder = &apiScaffolder{}\n\n// apiScaffolder contains configuration for generating scaffolding for Go type\n// representing the API and controller that implements the behavior for the API.\ntype apiScaffolder struct {\n\tconfig   config.Config\n\tresource resource.Resource\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n\n\t// force indicates whether to scaffold controller files even if it exists or not\n\tforce bool\n}\n\n// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations\nfunc NewAPIScaffolder(cfg config.Config, res resource.Resource, force bool) plugins.Scaffolder {\n\treturn &apiScaffolder{\n\t\tconfig:   cfg,\n\t\tresource: res,\n\t\tforce:    force,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *apiScaffolder) Scaffold() error {\n\tlog.Info(\"Writing scaffold for you to edit...\")\n\n\t// Load the boilerplate\n\tboilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)\n\tif err != nil {\n\t\tif errors.Is(err, afero.ErrFileNotFound) {\n\t\t\tlog.Warn(\"unable to find boilerplate file. \"+\n\t\t\t\t\"This file is used to generate the license header in the project.\\n\"+\n\t\t\t\t\"Note that controller-gen will also use this. Ensure that you \"+\n\t\t\t\t\"add the license file or configure your project accordingly\",\n\t\t\t\t\"file_path\", hack.DefaultBoilerplatePath, \"error\", err)\n\t\t\tboilerplate = []byte(\"\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"error scaffolding API/controller: failed to load boilerplate: %w\", err)\n\t\t}\n\t}\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t\tmachinery.WithBoilerplate(string(boilerplate)),\n\t\tmachinery.WithResource(&s.resource),\n\t)\n\n\t// Keep track of these values before the update\n\tdoAPI := s.resource.HasAPI()\n\tdoController := s.resource.HasController()\n\n\tif err := s.config.UpdateResource(s.resource); err != nil {\n\t\treturn fmt.Errorf(\"error updating resource: %w\", err)\n\t}\n\n\tif doAPI {\n\t\tif err := scaffold.Execute(\n\t\t\t&api.Types{Force: s.force},\n\t\t\t&api.Group{},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding APIs: %w\", err)\n\t\t}\n\t}\n\n\tif doController {\n\t\tif err := scaffold.Execute(\n\t\t\t&controllers.SuiteTest{Force: s.force},\n\t\t\t&controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force},\n\t\t\t&controllers.ControllerTest{Force: s.force, DoAPI: doAPI},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffolding controller: %w\", err)\n\t\t}\n\t}\n\n\tif err := scaffold.Execute(\n\t\t&cmd.MainUpdater{WireResource: doAPI, WireController: doController},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error updating cmd/main.go: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/doc.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package scaffolds contains libraries for scaffolding code to use with controller-runtime\npackage scaffolds\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/edit.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\tkustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2/scaffolds\"\n)\n\nvar _ plugins.Scaffolder = &editScaffolder{}\n\ntype editScaffolder struct {\n\tconfig     config.Config\n\tmultigroup bool\n\tnamespaced bool\n\tforce      bool\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewEditScaffolder returns a new Scaffolder for configuration edit operations\nfunc NewEditScaffolder(cfg config.Config, multigroup bool, namespaced bool, force bool) plugins.Scaffolder {\n\treturn &editScaffolder{\n\t\tconfig:     cfg,\n\t\tmultigroup: multigroup,\n\t\tnamespaced: namespaced,\n\t\tforce:      force,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *editScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *editScaffolder) Scaffold() error {\n\tfilename := \"Dockerfile\"\n\tbs, err := afero.ReadFile(s.fs.FS, filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading %q: %w\", filename, err)\n\t}\n\tstr := string(bs)\n\n\t// Track if we're toggling namespaced mode\n\twasNamespaced := s.config.IsNamespaced()\n\n\t// Update config flags\n\tif s.multigroup {\n\t\t_ = s.config.SetMultiGroup()\n\t} else {\n\t\t_ = s.config.ClearMultiGroup()\n\t}\n\n\tif s.namespaced {\n\t\t_ = s.config.SetNamespaced()\n\t} else {\n\t\t_ = s.config.ClearNamespaced()\n\t}\n\n\t// Scaffold appropriate RBAC and manager config based on namespaced flag\n\tif s.namespaced && !wasNamespaced {\n\t\t// Switching to namespaced layout: scaffold Role/RoleBinding and WATCH_NAMESPACE\n\t\tif rbacErr := s.scaffoldNamespacedRBAC(s.force); rbacErr != nil {\n\t\t\treturn fmt.Errorf(\"failed to scaffold namespaced RBAC: %w\", rbacErr)\n\t\t}\n\n\t\tif !s.force {\n\t\t\tfmt.Println()\n\t\t\tfmt.Println(\"Run with --force to update config/manager/manager.yaml with WATCH_NAMESPACE\")\n\t\t}\n\n\t\t// Check if project has webhooks and warn about scope mismatch\n\t\tif s.hasWebhooks() {\n\t\t\tlog.Warn(\"your project has webhooks which are cluster-scoped.\\n\" +\n\t\t\t\t\"You will need to manually configure namespaceSelector or objectSelector\")\n\t\t}\n\n\t\t// Print next steps\n\t\tfmt.Println()\n\t\tfmt.Println(\"Next steps:\")\n\t\tfmt.Println(\"1. Update cmd/main.go to configure namespace-scoped cache\")\n\t\tfmt.Println(\"2. Add namespace= to RBAC markers in existing controllers:\")\n\t\tfmt.Printf(\"   // +kubebuilder:rbac:groups=mygroup,resources=myresources,verbs=get;list,\"+\n\t\t\t\"namespace=%s-system\\n\", s.config.GetProjectName())\n\t\tfmt.Println(\"3. Run: make manifests\")\n\n\t\tif s.hasWebhooks() {\n\t\t\tfmt.Println(\"4. Configure namespaceSelector or objectSelector for webhooks\")\n\t\t}\n\n\t\tfmt.Println()\n\t\tfmt.Println(\"See: https://book.kubebuilder.io/migration/namespace-scoped.html\")\n\t} else if !s.namespaced && wasNamespaced {\n\t\t// Switching to cluster-scoped layout: scaffold ClusterRole/ClusterRoleBinding\n\t\tif rbacErr := s.scaffoldClusterRBAC(s.force); rbacErr != nil {\n\t\t\treturn fmt.Errorf(\"failed to scaffold cluster-scoped RBAC: %w\", rbacErr)\n\t\t}\n\n\t\tif !s.force {\n\t\t\tfmt.Println()\n\t\t\tfmt.Println(\"Run with --force to update config/manager/manager.yaml (remove WATCH_NAMESPACE)\")\n\t\t}\n\n\t\t// Print next steps\n\t\tfmt.Println()\n\t\tfmt.Println(\"Next steps:\")\n\t\tfmt.Println(\"1. Update cmd/main.go:\")\n\t\tfmt.Println(\"   - Remove getWatchNamespace() and setupCacheNamespaces() functions\")\n\t\tfmt.Println(\"   - Remove watchNamespace retrieval and cache configuration\")\n\t\tfmt.Println(\"2. Remove namespace= from RBAC markers in existing controllers\")\n\t\tfmt.Println(\"3. Run: make manifests\")\n\t\tfmt.Println()\n\t\tfmt.Println(\"See: https://book.kubebuilder.io/migration/namespace-scoped.html\")\n\t}\n\n\t// Check if the str is not empty, because when the file is already in desired format it will return empty string\n\t// because there is nothing to replace.\n\tif str != \"\" {\n\t\t// TODO: instead of writing it directly, we should use the scaffolding machinery for consistency\n\t\tif err = afero.WriteFile(s.fs.FS, filename, []byte(str), 0o644); err != nil {\n\t\t\treturn fmt.Errorf(\"error writing %q: %w\", filename, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *editScaffolder) scaffoldNamespacedRBAC(force bool) error {\n\t// Use the kustomize/v2 scaffolder to scaffold namespace-scoped RBAC and manager config\n\trbacScaffolder := kustomizecommonv2.NewEditScaffolder(s.config, true, force)\n\trbacScaffolder.InjectFS(s.fs)\n\tif err := rbacScaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold RBAC: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (s *editScaffolder) scaffoldClusterRBAC(force bool) error {\n\t// Use the kustomize/v2 scaffolder to scaffold cluster-scoped RBAC and manager config\n\trbacScaffolder := kustomizecommonv2.NewEditScaffolder(s.config, false, force)\n\trbacScaffolder.InjectFS(s.fs)\n\tif err := rbacScaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold RBAC: %w\", err)\n\t}\n\treturn nil\n}\n\n// hasWebhooks checks if any resources in the project have webhooks configured\nfunc (s *editScaffolder) hasWebhooks() bool {\n\tresources, err := s.config.GetResources()\n\tif err != nil {\n\t\treturn false\n\t}\n\tfor _, res := range resources {\n\t\tif res.Webhooks != nil && !res.Webhooks.IsEmpty() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/edit_integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nvar _ = Describe(\"Edit Scaffolding Integration Test\", func() {\n\tvar kbc *utils.TestContext\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\tkbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\t})\n\n\tAfterEach(func() {\n\t\tkbc.Destroy()\n\t})\n\n\tIt(\"should handle scope transitions comprehensively\", func() {\n\t\troleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role.yaml\")\n\t\troleBindingFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role_binding.yaml\")\n\t\tmanagerFile := filepath.Join(kbc.Dir, \"config\", \"manager\", \"manager.yaml\")\n\t\tprojectFile := filepath.Join(kbc.Dir, \"PROJECT\")\n\n\t\t// ========== Part 1: Cluster-scoped → Namespaced (without --force) ==========\n\t\tBy(\"initializing a cluster-scoped project\")\n\t\terr := kbc.Init(\n\t\t\t\"--plugins\", \"go/v4\",\n\t\t\t\"--project-version\", \"3\",\n\t\t\t\"--domain\", kbc.Domain,\n\t\t)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying initial state is cluster-scoped\")\n\t\tcontent, err := os.ReadFile(roleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\tBy(\"enabling namespaced layout without --force\")\n\t\terr = kbc.Edit(\"--namespaced\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying PROJECT file was updated\")\n\t\tcontent, err = os.ReadFile(projectFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(strings.Split(string(content), \"resources:\")[0]).To(ContainSubstring(\"namespaced: true\"))\n\n\t\tBy(\"verifying RBAC was changed to namespace-scoped\")\n\t\tcontent, err = os.ReadFile(roleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\t\tExpect(string(content)).NotTo(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\tcontent, err = os.ReadFile(roleBindingFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: RoleBinding\"))\n\n\t\tBy(\"verifying manager.yaml was NOT updated (no --force)\")\n\t\tcontent, err = os.ReadFile(managerFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).NotTo(ContainSubstring(\"WATCH_NAMESPACE\"),\n\t\t\t\"manager.yaml should not be updated without --force flag\")\n\n\t\t// ========== Part 2: Revert to cluster, then switch with --force ==========\n\t\tBy(\"reverting to cluster-scoped first\")\n\t\terr = kbc.Edit(\"--namespaced=false\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"re-enabling namespaced layout with --force to update manager.yaml\")\n\t\terr = kbc.Edit(\"--namespaced\", \"--force\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying manager.yaml was updated with WATCH_NAMESPACE\")\n\t\tcontent, err = os.ReadFile(managerFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"- name: WATCH_NAMESPACE\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"fieldRef:\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"fieldPath: metadata.namespace\"))\n\n\t\t// ========== Part 3: Create API and verify namespace RBAC markers ==========\n\t\tBy(\"creating an API in namespaced mode\")\n\t\terr = kbc.CreateAPI(\n\t\t\t\"--group\", kbc.Group,\n\t\t\t\"--version\", kbc.Version,\n\t\t\t\"--kind\", kbc.Kind,\n\t\t\t\"--resource\",\n\t\t\t\"--controller\",\n\t\t\t\"--make=false\",\n\t\t)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying controller has namespace parameter in RBAC markers\")\n\t\tcontrollerFile := filepath.Join(kbc.Dir, \"internal\", \"controller\",\n\t\t\tstrings.ToLower(kbc.Kind)+\"_controller.go\")\n\t\tcontent, err = os.ReadFile(controllerFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\texpectedNamespace := \"e2e-\" + kbc.TestSuffix + \"-system\"\n\t\tExpect(string(content)).To(ContainSubstring(\"namespace=\"+expectedNamespace),\n\t\t\t\"Controller RBAC markers should include namespace parameter\")\n\n\t\tBy(\"verifying CRD admin/editor/viewer roles are namespace-scoped\")\n\t\tadminRoleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\",\n\t\t\tstrings.ToLower(kbc.Kind)+\"_admin_role.yaml\")\n\t\teditorRoleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\",\n\t\t\tstrings.ToLower(kbc.Kind)+\"_editor_role.yaml\")\n\t\tviewerRoleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\",\n\t\t\tstrings.ToLower(kbc.Kind)+\"_viewer_role.yaml\")\n\n\t\tcontent, err = os.ReadFile(adminRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tcontent, err = os.ReadFile(editorRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tcontent, err = os.ReadFile(viewerRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\t// ========== Part 4: Switch to cluster-scoped and verify all roles updated ==========\n\t\tBy(\"switching to cluster-scoped with --force\")\n\t\terr = kbc.Edit(\"--namespaced=false\", \"--force\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying PROJECT file was updated\")\n\t\tcontent, err = os.ReadFile(projectFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tprojectContent := string(content)\n\t\tbeforeResources := strings.Split(projectContent, \"resources:\")[0]\n\t\tif strings.Contains(beforeResources, \"namespaced:\") {\n\t\t\tExpect(beforeResources).To(ContainSubstring(\"namespaced: false\"))\n\t\t}\n\n\t\tBy(\"verifying manager RBAC was changed to cluster-scoped\")\n\t\tcontent, err = os.ReadFile(roleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\tcontent, err = os.ReadFile(roleBindingFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRoleBinding\"))\n\n\t\tBy(\"verifying manager.yaml was updated (WATCH_NAMESPACE removed)\")\n\t\tcontent, err = os.ReadFile(managerFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).NotTo(ContainSubstring(\"WATCH_NAMESPACE\"))\n\n\t\tBy(\"verifying CRD roles were updated to cluster-scoped\")\n\t\tcontent, err = os.ReadFile(adminRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\tcontent, err = os.ReadFile(editorRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\tcontent, err = os.ReadFile(viewerRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\t// ========== Part 5: Switch back to namespaced and verify again ==========\n\t\tBy(\"switching back to namespace-scoped with --force\")\n\t\terr = kbc.Edit(\"--namespaced\", \"--force\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"verifying all roles reverted to namespace-scoped\")\n\t\tcontent, err = os.ReadFile(roleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tcontent, err = os.ReadFile(adminRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tcontent, err = os.ReadFile(editorRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tcontent, err = os.ReadFile(viewerRoleFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\n\t\tBy(\"verifying manager.yaml has WATCH_NAMESPACE again\")\n\t\tcontent, err = os.ReadFile(managerFile)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(ContainSubstring(\"WATCH_NAMESPACE\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\tkustomizecommonv2 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/github\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils\"\n)\n\nconst (\n\t// GolangciLintVersion is the golangci-lint version to be used in the project\n\tGolangciLintVersion = \"v2.8.0\"\n\t// ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project\n\tControllerRuntimeVersion = \"v0.23.3\"\n\t// ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project\n\tControllerToolsVersion = \"v0.20.1\"\n\n\timageName = \"controller:latest\"\n)\n\nvar _ plugins.Scaffolder = &initScaffolder{}\n\nvar kustomizeVersion string\n\ntype initScaffolder struct {\n\tconfig          config.Config\n\tboilerplatePath string\n\tlicense         string\n\towner           string\n\tcommandName     string\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewInitScaffolder returns a new Scaffolder for project initialization operations\nfunc NewInitScaffolder(cfg config.Config, license, owner, commandName string) plugins.Scaffolder {\n\treturn &initScaffolder{\n\t\tconfig:          cfg,\n\t\tboilerplatePath: hack.DefaultBoilerplatePath,\n\t\tlicense:         license,\n\t\towner:           owner,\n\t\tcommandName:     commandName,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *initScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// getControllerRuntimeReleaseBranch converts the ControllerRuntime semantic versioning string to a\n// release branch string. Example input: \"v0.17.0\" -> Output: \"release-0.17\"\nfunc getControllerRuntimeReleaseBranch() string {\n\tv := strings.TrimPrefix(ControllerRuntimeVersion, \"v\")\n\ttmp := strings.Split(v, \".\")\n\n\tif len(tmp) < 2 {\n\t\tfmt.Println(\"Invalid version format. Expected at least major and minor version numbers.\")\n\t\treturn \"\"\n\t}\n\treleaseBranch := fmt.Sprintf(\"release-%s.%s\", tmp[0], tmp[1])\n\treturn releaseBranch\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *initScaffolder) Scaffold() error {\n\tlog.Info(\"Writing scaffold for you to edit...\")\n\n\t// Initialize the machinery.Scaffold that will write the boilerplate file to disk\n\t// The boilerplate file needs to be scaffolded as a separate step as it is going to\n\t// be used by the rest of the files, even those scaffolded in this command call.\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\tif s.license != \"none\" {\n\t\tbpFile := &hack.Boilerplate{\n\t\t\tLicense: s.license,\n\t\t\tOwner:   s.owner,\n\t\t}\n\t\tbpFile.Path = s.boilerplatePath\n\t\tif err := scaffold.Execute(bpFile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to execute boilerplate: %w\", err)\n\t\t}\n\n\t\tboilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, afero.ErrFileNotFound) {\n\t\t\t\tlog.Warn(\"unable to find boilerplate file. \"+\n\t\t\t\t\t\"This file is used to generate the license header in the project.\\n\"+\n\t\t\t\t\t\"Note that controller-gen will also use this. Ensure that you \"+\n\t\t\t\t\t\"add the license file or configure your project accordingly\",\n\t\t\t\t\t\"file_path\", s.boilerplatePath,\n\t\t\t\t\t\"error\", err)\n\t\t\t\tboilerplate = []byte(\"\")\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"failed to load boilerplate: %w\", err)\n\t\t\t}\n\t\t}\n\t\t// Initialize the machinery.Scaffold that will write the files to disk\n\t\tscaffold = machinery.NewScaffold(s.fs,\n\t\t\tmachinery.WithConfig(s.config),\n\t\t\tmachinery.WithBoilerplate(string(boilerplate)),\n\t\t)\n\t} else {\n\t\ts.boilerplatePath = \"\"\n\t\t// Initialize the machinery.Scaffold without boilerplate\n\t\tscaffold = machinery.NewScaffold(s.fs,\n\t\t\tmachinery.WithConfig(s.config),\n\t\t)\n\t}\n\n\t// If the KustomizeV2 was used to do the scaffold then\n\t// we need to ensure that we use its supported Kustomize Version\n\t// in order to support it\n\tkustomizev2 := kustomizecommonv2.Plugin{}\n\tgov4 := \"go.kubebuilder.io/v4\"\n\tpluginKeyForKustomizeV2 := plugin.KeyFor(kustomizev2)\n\n\tfor _, pluginKey := range s.config.GetPluginChain() {\n\t\tif pluginKey == pluginKeyForKustomizeV2 || pluginKey == gov4 {\n\t\t\tkustomizeVersion = kustomizecommonv2.KustomizeVersion\n\t\t\tbreak\n\t\t}\n\t}\n\n\terr := scaffold.Execute(\n\t\t&cmd.Main{\n\t\t\tControllerRuntimeVersion: ControllerRuntimeVersion,\n\t\t},\n\t\t&templates.GoMod{\n\t\t\tControllerRuntimeVersion: ControllerRuntimeVersion,\n\t\t},\n\t\t&templates.GitIgnore{},\n\t\t&templates.Makefile{\n\t\t\tImage:                    imageName,\n\t\t\tBoilerplatePath:          s.boilerplatePath,\n\t\t\tControllerToolsVersion:   ControllerToolsVersion,\n\t\t\tKustomizeVersion:         kustomizeVersion,\n\t\t\tGolangciLintVersion:      GolangciLintVersion,\n\t\t\tControllerRuntimeVersion: ControllerRuntimeVersion,\n\t\t\tEnvtestVersion:           getControllerRuntimeReleaseBranch(),\n\t\t},\n\t\t&templates.Dockerfile{},\n\t\t&templates.DockerIgnore{},\n\t\t&templates.Readme{CommandName: s.commandName},\n\t\t&templates.Agents{CommandName: s.commandName},\n\t\t&templates.Golangci{},\n\t\t&templates.CustomGcl{\n\t\t\tGolangciLintVersion: GolangciLintVersion,\n\t\t},\n\t\t&e2e.Test{},\n\t\t&e2e.WebhookTestUpdater{WireWebhook: false},\n\t\t&e2e.SuiteTest{},\n\t\t&github.E2eTestCi{},\n\t\t&github.TestCi{},\n\t\t&github.LintCi{\n\t\t\tGolangciLintVersion: GolangciLintVersion,\n\t\t},\n\t\t&utils.Utils{},\n\t\t&templates.DevContainer{},\n\t\t&templates.DevContainerPostInstallScript{},\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to execute init scaffold: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/init_integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nvar _ = Describe(\"Init Scaffolding Integration Test\", func() {\n\tvar kbc *utils.TestContext\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\tkbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\t})\n\n\tAfterEach(func() {\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"cluster-scoped init (default)\", func() {\n\t\tIt(\"should scaffold cluster-scoped configuration\", func() {\n\t\t\tBy(\"initializing a cluster-scoped project\")\n\t\t\terr := kbc.Init(\n\t\t\t\t\"--plugins\", \"go/v4\",\n\t\t\t\t\"--project-version\", \"3\",\n\t\t\t\t\"--domain\", kbc.Domain,\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying PROJECT file does not have namespaced flag\")\n\t\t\tprojectFile := filepath.Join(kbc.Dir, \"PROJECT\")\n\t\t\tcontent, err := os.ReadFile(projectFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprojectContent := string(content)\n\t\t\t// Check root level doesn't have namespaced: true\n\t\t\tbeforeResources := strings.Split(projectContent, \"resources:\")[0]\n\t\t\tif strings.Contains(beforeResources, \"namespaced:\") {\n\t\t\t\tExpect(beforeResources).NotTo(ContainSubstring(\"namespaced: true\"))\n\t\t\t}\n\n\t\t\tBy(\"verifying RBAC is cluster-scoped\")\n\t\t\troleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role.yaml\")\n\t\t\tcontent, err = os.ReadFile(roleFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\t\troleBindingFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role_binding.yaml\")\n\t\t\tcontent, err = os.ReadFile(roleBindingFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).To(ContainSubstring(\"kind: ClusterRoleBinding\"))\n\n\t\t\tBy(\"verifying manager.yaml does NOT have WATCH_NAMESPACE\")\n\t\t\tmanagerFile := filepath.Join(kbc.Dir, \"config\", \"manager\", \"manager.yaml\")\n\t\t\tcontent, err = os.ReadFile(managerFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"WATCH_NAMESPACE\"))\n\n\t\t\tBy(\"verifying cmd/main.go does NOT have namespace helper functions\")\n\t\t\tmainFile := filepath.Join(kbc.Dir, \"cmd\", \"main.go\")\n\t\t\tcontent, err = os.ReadFile(mainFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tmainContent := string(content)\n\t\t\tExpect(mainContent).NotTo(ContainSubstring(\"func getWatchNamespace()\"))\n\t\t\tExpect(mainContent).NotTo(ContainSubstring(\"func setupCacheNamespaces\"))\n\t\t})\n\t})\n\n\tContext(\"namespace-scoped init (--namespaced)\", func() {\n\t\tIt(\"should scaffold namespace-scoped configuration\", func() {\n\t\t\tBy(\"initializing a namespace-scoped project\")\n\t\t\terr := kbc.Init(\n\t\t\t\t\"--plugins\", \"go/v4\",\n\t\t\t\t\"--project-version\", \"3\",\n\t\t\t\t\"--domain\", kbc.Domain,\n\t\t\t\t\"--namespaced\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying PROJECT file has namespaced: true\")\n\t\t\tprojectFile := filepath.Join(kbc.Dir, \"PROJECT\")\n\t\t\tcontent, err := os.ReadFile(projectFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprojectContent := string(content)\n\t\t\t// Check root level has namespaced: true\n\t\t\tbeforeResources := strings.Split(projectContent, \"resources:\")[0]\n\t\t\tExpect(beforeResources).To(ContainSubstring(\"namespaced: true\"))\n\n\t\t\tBy(\"verifying RBAC is namespace-scoped\")\n\t\t\troleFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role.yaml\")\n\t\t\tcontent, err = os.ReadFile(roleFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\troleContent := string(content)\n\t\t\tExpect(roleContent).To(ContainSubstring(\"kind: Role\"))\n\t\t\tExpect(roleContent).NotTo(ContainSubstring(\"kind: ClusterRole\"))\n\n\t\t\troleBindingFile := filepath.Join(kbc.Dir, \"config\", \"rbac\", \"role_binding.yaml\")\n\t\t\tcontent, err = os.ReadFile(roleBindingFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tbindingContent := string(content)\n\t\t\tExpect(bindingContent).To(ContainSubstring(\"kind: RoleBinding\"))\n\t\t\tExpect(bindingContent).NotTo(ContainSubstring(\"kind: ClusterRoleBinding\"))\n\n\t\t\tBy(\"verifying manager.yaml has WATCH_NAMESPACE environment variable\")\n\t\t\tmanagerFile := filepath.Join(kbc.Dir, \"config\", \"manager\", \"manager.yaml\")\n\t\t\tcontent, err = os.ReadFile(managerFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tmanagerContent := string(content)\n\t\t\tExpect(managerContent).To(ContainSubstring(\"- name: WATCH_NAMESPACE\"))\n\t\t\tExpect(managerContent).To(ContainSubstring(\"fieldRef:\"))\n\t\t\tExpect(managerContent).To(ContainSubstring(\"fieldPath: metadata.namespace\"))\n\n\t\t\tBy(\"verifying cmd/main.go has getWatchNamespace helper function\")\n\t\t\tmainFile := filepath.Join(kbc.Dir, \"cmd\", \"main.go\")\n\t\t\tcontent, err = os.ReadFile(mainFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tmainContent := string(content)\n\t\t\tExpect(mainContent).To(ContainSubstring(\"func getWatchNamespace()\"))\n\t\t\tExpect(mainContent).To(ContainSubstring(\"WATCH_NAMESPACE\"))\n\n\t\t\tBy(\"verifying cmd/main.go has setupCacheNamespaces helper function\")\n\t\t\tExpect(mainContent).To(ContainSubstring(\"func setupCacheNamespaces\"))\n\t\t\tExpect(mainContent).To(ContainSubstring(\"DefaultNamespaces\"))\n\t\t\tExpect(mainContent).To(ContainSubstring(\"cache.Config\"))\n\n\t\t\tBy(\"verifying cmd/main.go uses helper functions in main()\")\n\t\t\tExpect(mainContent).To(ContainSubstring(\"watchNamespace, err := getWatchNamespace()\"))\n\t\t\tExpect(mainContent).To(ContainSubstring(\"mgrOptions.Cache = setupCacheNamespaces(watchNamespace)\"))\n\n\t\t\tBy(\"creating an API to verify controller scaffolding\")\n\t\t\terr = kbc.CreateAPI(\n\t\t\t\t\"--group\", kbc.Group,\n\t\t\t\t\"--version\", kbc.Version,\n\t\t\t\t\"--kind\", kbc.Kind,\n\t\t\t\t\"--resource\",\n\t\t\t\t\"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying controller has namespace parameter in RBAC markers\")\n\t\t\tcontrollerFile := filepath.Join(kbc.Dir, \"internal\", \"controller\",\n\t\t\t\tstrings.ToLower(kbc.Kind)+\"_controller.go\")\n\t\t\tcontent, err = os.ReadFile(controllerFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontrollerContent := string(content)\n\n\t\t\texpectedNamespace := \"e2e-\" + kbc.TestSuffix + \"-system\"\n\t\t\tExpect(controllerContent).To(ContainSubstring(\"namespace=\"+expectedNamespace),\n\t\t\t\t\"Controller RBAC markers should include namespace parameter\")\n\n\t\t\tBy(\"verifying admin/editor/viewer roles are namespace-scoped\")\n\t\t\teditorFile := filepath.Join(kbc.Dir, \"config\", \"rbac\",\n\t\t\t\tstrings.ToLower(kbc.Kind)+\"_editor_role.yaml\")\n\t\t\tcontent, err = os.ReadFile(editorFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).To(ContainSubstring(\"kind: Role\"))\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"kind: ClusterRole\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/agents.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Agents{}\n\n// Agents scaffolds an AGENTS.md file\ntype Agents struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// CommandName stores the name of the bin used\n\tCommandName string\n\t// IsKubebuilderCLI indicates if kubebuilder CLI is being used (vs operator-sdk, etc)\n\tIsKubebuilderCLI bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Agents) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"AGENTS.md\"\n\t}\n\n\t// Check if using Kubebuilder CLI\n\tif f.CommandName != \"\" {\n\t\tf.IsKubebuilderCLI = strings.Contains(f.CommandName, \"kubebuilder\")\n\t}\n\n\tf.TemplateBody = agentsFileTemplate\n\n\treturn nil\n}\n\n//nolint:lll\nconst agentsFileTemplate = `# {{ .ProjectName }} - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n` + \"```\" + `\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n` + \"```\" + `\n\n**Multi-group layout** (for projects with multiple API groups):\n` + \"```\" + `\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n` + \"```\" + `\n\nMulti-group layout organizes APIs by group name (e.g., ` + \"`batch`\" + `, ` + \"`apps`\" + `). Check the ` + \"`PROJECT`\" + ` file for ` + \"`multigroup: true`\" + `.\n\n**To convert to multi-group layout:**\n1. Run: ` + \"`{{ .CommandName }} edit --multigroup=true`\" + `\n2. Move APIs: ` + \"`mkdir -p api/<group> && mv api/<version> api/<group>/`\" + `\n3. Move controllers: ` + \"`mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\" + `\n4. Move webhooks (if present): ` + \"`mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\" + `\n5. Update import paths in all files\n6. Fix ` + \"`path`\" + ` in ` + \"`PROJECT`\" + ` file for each resource\n7. Update test suite CRD paths (add one more ` + \"`..`\" + ` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- ` + \"`config/crd/bases/*.yaml`\" + ` - from ` + \"`make manifests`\" + `\n- ` + \"`config/rbac/role.yaml`\" + ` - from ` + \"`make manifests`\" + `\n- ` + \"`config/webhook/manifests.yaml`\" + ` - from ` + \"`make manifests`\" + `\n- ` + \"`**/zz_generated.*.go`\" + ` - from ` + \"`make generate`\" + `\n- ` + \"`PROJECT`\" + ` - from ` + \"`{{ .CommandName }} [OPTIONS]`\" + `\n\n### Never Remove Scaffold Markers\nDo NOT delete ` + \"`// +kubebuilder:scaffold:*`\" + ` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use ` + \"`{{ .CommandName }} create api`\" + ` and ` + \"`{{ .CommandName }} create webhook`\" + ` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing ` + \"`*_types.go`\" + ` or markers:**\n` + \"```\" + `\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n` + \"```\" + `\n\n**After editing ` + \"`*.go`\" + ` files:**\n` + \"```\" + `\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n` + \"```\" + `\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n` + \"```bash\" + `\n{{ .CommandName }} create api --group <group> --version <version> --kind <Kind>\n` + \"```\" + `{{ if .IsKubebuilderCLI }}\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n` + \"```bash\" + `\n# Example: deploying memcached\n{{ .CommandName }} create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n` + \"```\" + `\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n{{ end }}\n\n### Create Webhooks\n` + \"```bash\" + `\n# Validation + defaulting\n{{ .CommandName }} create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\n{{ .CommandName }} create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n` + \"```\" + `\n\n### Controller for Core Kubernetes Types\n` + \"```bash\" + `\n# Watch Pods\n{{ .CommandName }} create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\n{{ .CommandName }} create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n` + \"```\" + `\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n` + \"```bash\" + `\n# Example: watching cert-manager Certificate resources\n{{ .CommandName }} create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n` + \"```\" + `\n\n**Note:** Use ` + \"`--external-api-module=<module>@<version>`\" + ` only if you need a specific version. Otherwise, omit ` + \"`@<version>`\" + ` to use what's in go.mod.\n\n### Webhook for External Types\n\n` + \"```bash\" + `\n# Example: validating external resources\n{{ .CommandName }} create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n` + \"```\" + `\n\n## Testing & Development\n\n` + \"```bash\" + `\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n` + \"```\" + `\n\nTests use **Ginkgo + Gomega** (BDD style). Check ` + \"`suite_test.go`\" + ` for setup.\n\n## Deployment Workflow\n\n` + \"```bash\" + `\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n` + \"```\" + `\n\n### API Design\n\n**Key markers for** ` + \"`api/<version>/*_types.go`\" + `:\n\n` + \"```go\" + `\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n` + \"```\" + `\n\n- **Use** ` + \"`metav1.Condition`\" + ` for status (not custom string fields)\n- **Use predefined types**: ` + \"`metav1.Time`\" + ` instead of ` + \"`string`\" + ` for dates\n- **Follow K8s API conventions**: Standard field names (` + \"`spec`\" + `, ` + \"`status`\" + `, ` + \"`metadata`\" + `)\n\n### Controller Design\n\n**RBAC markers in** ` + \"`internal/controller/*_controller.go`\" + `:\n\n` + \"```go\" + `\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n` + \"```\" + `\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: ` + \"`r.Get(ctx, req.NamespacedName, obj)`\" + ` before ` + \"`r.Update`\" + ` to avoid conflicts\n- **Structured logging**: ` + \"`log := log.FromContext(ctx); log.Info(\\\"msg\\\", \\\"key\\\", val)`\" + `\n- **Owner references**: Enable automatic garbage collection (` + \"`SetControllerReference`\" + `)\n- **Watch secondary resources**: Use ` + \"`.Owns()`\" + ` or ` + \"`.Watches()`\" + `, not just ` + \"`RequeueAfter`\" + `\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (` + \"`\\\"Deployment could not create Pod\\\"`\" + `) or omitted (` + \"`\\\"Could not create Pod\\\"`\" + `)\n- Past tense: ` + \"`\\\"Could not delete Pod\\\"`\" + ` not ` + \"`\\\"Cannot delete Pod\\\"`\" + `\n- Specify object type: ` + \"`\\\"Deleted Pod\\\"`\" + ` not ` + \"`\\\"Deleted\\\"`\" + `\n- Balanced key-value pairs\n\n` + \"```go\" + `\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n` + \"```\" + `\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: ` + \"`--defaulting --programmatic-validation --conversion`\" + `\n- **When` + \"`--force`\" + `is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (` + \"`--conversion --spoke v2`\" + `)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: ` + \"`--group crew --version v1 --kind Captain --conversion --spoke v2`\" + ` (v1 is hub, v2 is spoke){{ if .IsKubebuilderCLI }}\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n` + \"```bash\" + `\n{{ .CommandName }} create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n` + \"```\" + `\n\nGenerated code includes: status conditions (` + \"`metav1.Condition`\" + `), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n` + \"```bash\" + `\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n` + \"```\" + `\n\n**Key points:**\n- The ` + \"`dist/install.yaml`\" + ` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need ` + \"`kubectl`\" + ` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n` + \"```bash\" + `\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n` + \"```\" + `\n\n### Option 2: Helm Chart\n\n` + \"```bash\" + `\n{{ .CommandName }} edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\n{{ .CommandName }} edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n` + \"```\" + `\n\n**For development:**\n` + \"```bash\" + `\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n` + \"```\" + `\n\n**For end users/production:**\n` + \"```bash\" + `\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n` + \"```\" + `\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in ` + \"`<output-dir>/chart/values.yaml`\" + ` and ` + \"`<output-dir>/chart/manager/manager.yaml`\" + `\n2. Re-run: ` + \"`{{ .CommandName }} edit --plugins=helm/v2-alpha --force`\" + ` (use same ` + \"`--output-dir`\" + ` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n` + \"```bash\" + `\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n` + \"```\" + `{{ end }}\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Group{}\n\n// Group scaffolds the file that defines the registration methods for a certain group and version\ntype Group struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Group) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[group]\", \"%[version]\", \"groupversion_info.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[version]\", \"groupversion_info.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\tf.TemplateBody = groupTemplate\n\n\treturn nil\n}\n\n//nolint:lll\nconst groupTemplate = `{{ .Boilerplate }}\n\n// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group.\n// +kubebuilder:object:generate=true\n// +groupName={{ .Resource.QualifiedGroup }}\npackage {{ .Resource.Version }}\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"{{ .Resource.QualifiedGroup }}\", Version: \"{{ .Resource.Version }}\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/api/hub.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Hub{}\n\n// Hub scaffolds the file that defines hub\n//\n\ntype Hub struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements file.Template\nfunc (f *Hub) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[group]\", \"%[version]\", \"%[kind]_conversion.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[version]\", \"%[kind]_conversion.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = hubTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst hubTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// Hub marks this type as a conversion hub.\nfunc (*{{ .Resource.Kind }}) Hub() {}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/api/spoke.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Spoke{}\n\n// Spoke scaffolds the file that defines spoke version conversion\ntype Spoke struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\tForce        bool\n\tSpokeVersion string\n}\n\n// SetTemplateDefaults implements file.Template\nfunc (f *Spoke) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\t// Use SpokeVersion for dynamic file path generation\n\t\t\tf.Path = filepath.Join(\"api\", f.Resource.Group, f.SpokeVersion, \"%[kind]_conversion.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"api\", f.SpokeVersion, \"%[kind]_conversion.go\")\n\t\t}\n\t}\n\n\t// Replace placeholders in the path\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(\"Creating spoke conversion file\", \"path\", f.Path)\n\n\tf.TemplateBody = spokeTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst spokeTemplate = `{{ .Boilerplate }}\n\npackage {{ .SpokeVersion }}\n\nimport (\n\t\"log\"\n\n    \"sigs.k8s.io/controller-runtime/pkg/conversion\"\n    {{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n)\n\n// ConvertTo converts this {{ .Resource.Kind }} ({{ .SpokeVersion }}) to the Hub version ({{ .Resource.Version }}).\nfunc (src *{{ .Resource.Kind }}) ConvertTo(dstRaw conversion.Hub) error {\n\tdst := dstRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})\n\tlog.Printf(\"ConvertTo: Converting {{ .Resource.Kind }} from Spoke version {{ .SpokeVersion }} to Hub version {{ .Resource.Version }};\" +\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\t\n\t// TODO(user): Implement conversion logic from {{ .SpokeVersion }} to {{ .Resource.Version }}\n\t// Example: Copying Spec fields\n\t// dst.Spec.Size = src.Spec.Replicas\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n\n// ConvertFrom converts the Hub version ({{ .Resource.Version }}) to this {{ .Resource.Kind }} ({{ .SpokeVersion }}).\nfunc (dst *{{ .Resource.Kind }}) ConvertFrom(srcRaw conversion.Hub) error {\n\tsrc := srcRaw.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }})\n\tlog.Printf(\"ConvertFrom: Converting {{ .Resource.Kind }} from Hub version {{ .Resource.Version }} to Spoke version {{ .SpokeVersion }};\" +\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from {{ .Resource.Version }} to {{ .SpokeVersion }}\n\t// Example: Copying Spec fields\n\t// dst.Spec.Replicas = src.Spec.Size\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Types{}\n\n// Types scaffolds the file that defines the schema for a CRD\n//\n\ntype Types struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Types) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[group]\", \"%[version]\", \"%[kind]_types.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"api\", \"%[version]\", \"%[kind]_types.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = typesTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.Error\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst typesTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }}\ntype {{ .Resource.Kind }}Spec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update\n\t// +optional\t\n\tFoo *string ` + \"`\" + `json:\"foo,omitempty\"` + \"`\" + `\n}\n\n// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}.\ntype {{ .Resource.Kind }}Status struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the {{ .Resource.Kind }} resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition ` + \"`\" + `json:\"conditions,omitempty\"` + \"`\" + `\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }}\n// +kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster\n{{- else if not .Resource.API.Namespaced }}\n// +kubebuilder:resource:scope=Cluster\n{{- else if not .Resource.IsRegularPlural }}\n// +kubebuilder:resource:path={{ .Resource.Plural }}\n{{- end }}\n\n// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API\ntype {{ .Resource.Kind }} struct {\n\tmetav1.TypeMeta   ` + \"`\" + `json:\",inline\"` + \"`\" + `\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta ` + \"`\" + `json:\"metadata,omitzero\"` + \"`\" + `\n\n\t// spec defines the desired state of {{ .Resource.Kind }}\n\t// +required\n\tSpec   {{ .Resource.Kind }}Spec   ` + \"`\" + `json:\"spec\"` + \"`\" + `\n\n\t// status defines the observed state of {{ .Resource.Kind }}\n\t// +optional\n\tStatus {{ .Resource.Kind }}Status ` + \"`\" + `json:\"status,omitzero\"` + \"`\" + `\n}\n\n// +kubebuilder:object:root=true\n\n// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}\ntype {{ .Resource.Kind }}List struct {\n\tmetav1.TypeMeta ` + \"`\" + `json:\",inline\"` + \"`\" + `\n\tmetav1.ListMeta ` + \"`\" + `json:\"metadata,omitzero\"` + \"`\" + `\n\tItems           []{{ .Resource.Kind }} ` + \"`\" + `json:\"items\"` + \"`\" + `\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{})\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/api/types_updater.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage api\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst (\n\tstorageVersionMarker = \"\\n// +kubebuilder:storageversion\"\n)\n\nvar _ machinery.Template = &TypesUpdater{}\n\n// TypesUpdater updates an existing API types file to add conversion-related markers\ntype TypesUpdater struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n}\n\n// GetPath implements file.Builder\nfunc (f *TypesUpdater) GetPath() string {\n\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\tf.Path = filepath.Join(\"api\", \"%[group]\", \"%[version]\", \"%[kind]_types.go\")\n\t} else {\n\t\tf.Path = filepath.Join(\"api\", \"%[version]\", \"%[kind]_types.go\")\n\t}\n\n\treturn f.Resource.Replacer().Replace(f.Path)\n}\n\n// GetIfExistsAction implements file.Builder\nfunc (*TypesUpdater) GetIfExistsAction() machinery.IfExistsAction {\n\treturn machinery.OverwriteFile\n}\n\n// SetTemplateDefaults implements file.Template\nfunc (f *TypesUpdater) SetTemplateDefaults() error {\n\tfilePath := f.GetPath()\n\n\t// Read the existing file\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tlog.Error(\"failed to read types file\", \"file\", filePath, \"error\", err)\n\t\treturn fmt.Errorf(\"failed to read types file: %w\", err)\n\t}\n\n\tfileContent := string(content)\n\tmodified := false\n\n\t// Check if we need to add storage version marker for conversion webhooks\n\tif f.Resource.HasConversionWebhook() && !bytes.Contains(content, []byte(\"+kubebuilder:storageversion\")) {\n\t\tfileContent = f.addStorageVersionMarker(fileContent)\n\t\tmodified = true\n\t}\n\n\tif !modified {\n\t\t// No updates needed, skip writing\n\t\treturn nil\n\t}\n\n\tf.TemplateBody = fileContent\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n// addStorageVersionMarker adds the storage version marker after +kubebuilder:object:root=true\nfunc (f *TypesUpdater) addStorageVersionMarker(content string) string {\n\t// Try to match the specific Kind's type definition (handles multigroup with multiple types)\n\ttypePatternStr := fmt.Sprintf(\n\t\t`(?m)^(//\\s*\\+kubebuilder:object:root=true)\\s*$(?:\\s*//.*$)*\\s*type\\s+%s\\s+struct`,\n\t\tf.Resource.Kind)\n\ttypePattern := regexp.MustCompile(typePatternStr)\n\n\tif match := typePattern.FindStringSubmatch(content); len(match) > 1 {\n\t\trootMarker := match[1]\n\t\tidx := strings.Index(content, rootMarker)\n\t\tif idx != -1 {\n\t\t\tinsertPos := idx + len(rootMarker)\n\t\t\treturn content[:insertPos] + storageVersionMarker + content[insertPos:]\n\t\t}\n\t}\n\n\t// Fallback: find first +kubebuilder:object:root=true marker\n\tsimplePattern := regexp.MustCompile(`(?m)^(//\\s*\\+kubebuilder:object:root=true)\\s*$`)\n\tif match := simplePattern.FindStringIndex(content); match != nil {\n\t\tlog.Info(\"Adding storage version marker to first type definition\",\n\t\t\t\"kind\", f.Resource.Kind)\n\t\treturn content[:match[1]] + storageVersionMarker + content[match[1]:]\n\t}\n\n\tlog.Warn(\"Could not find +kubebuilder:object:root=true marker\",\n\t\t\"kind\", f.Resource.Kind,\n\t\t\"suggestion\", \"Manually add // +kubebuilder:storageversion\")\n\n\treturn content\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/cmd/main.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst defaultMainPath = \"cmd/main.go\"\n\nvar _ machinery.Template = &Main{}\n\n// Main scaffolds a file that defines the controller manager entry point\ntype Main struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.DomainMixin\n\tmachinery.RepositoryMixin\n\tmachinery.NamespacedMixin\n\n\tControllerRuntimeVersion string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Main) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(defaultMainPath)\n\t}\n\n\tf.TemplateBody = fmt.Sprintf(mainTemplate,\n\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t\tmachinery.NewMarkerFor(f.Path, setupMarker),\n\t)\n\n\treturn nil\n}\n\nvar _ machinery.Inserter = &MainUpdater{}\n\n// MainUpdater updates cmd/main.go to run Controllers\ntype MainUpdater struct {\n\tmachinery.RepositoryMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n\n\t// Flags to indicate which parts need to be included when updating the file\n\tWireResource, WireController, WireWebhook bool\n\n\t// Deprecated - The flag should be removed from go/v5\n\t// IsLegacyPath indicates if webhooks should be scaffolded under the API.\n\t// Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.\n\t// This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.\n\tIsLegacyPath bool\n}\n\n// GetPath implements file.Builder\nfunc (*MainUpdater) GetPath() string {\n\treturn defaultMainPath\n}\n\n// GetIfExistsAction implements file.Builder\nfunc (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction {\n\treturn machinery.OverwriteFile\n}\n\nconst (\n\timportMarker    = \"imports\"\n\taddSchemeMarker = \"scheme\"\n\tsetupMarker     = \"builder\"\n)\n\n// GetMarkers implements file.Inserter\nfunc (f *MainUpdater) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(defaultMainPath, importMarker),\n\t\tmachinery.NewMarkerFor(defaultMainPath, addSchemeMarker),\n\t\tmachinery.NewMarkerFor(defaultMainPath, setupMarker),\n\t}\n}\n\nconst (\n\tapiImportCodeFragment = `%s \"%s\"\n`\n\tcontrollerImportCodeFragment = `\"%s/internal/controller\"\n`\n\twebhookImportCodeFragment = `%s \"%s/internal/webhook/%s\"\n`\n\tmultiGroupWebhookImportCodeFragment = `%s \"%s/internal/webhook/%s/%s\"\n`\n\tmultiGroupControllerImportCodeFragment = `%scontroller \"%s/internal/controller/%s\"\n`\n\taddschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme))\n`\n\treconcilerSetupCodeFragment = `if err := (&controller.%sReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"%s\")\n\t\tos.Exit(1)\n\t}\n`\n\tmultiGroupReconcilerSetupCodeFragment = `if err := (&%scontroller.%sReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"%s\")\n\t\tos.Exit(1)\n\t}\n`\n\twebhookSetupCodeFragmentLegacy = `// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"%s\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n`\n\n\twebhookSetupCodeFragment = `// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := %s.Setup%sWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"%s\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n`\n)\n\n// GetCodeFragments implements file.Inserter\nfunc (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap {\n\tfragments := make(machinery.CodeFragmentsMap, 3)\n\n\t// If resource is not being provided we are creating the file, not updating it\n\tif f.Resource == nil {\n\t\treturn fragments\n\t}\n\n\t// Generate import code fragments\n\timports := make([]string, 0)\n\tif f.WireResource || f.Resource.IsExternal() {\n\t\timports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))\n\t}\n\tif f.WireWebhook && !f.IsLegacyPath {\n\t\tif !f.MultiGroup || f.Resource.Group == \"\" {\n\t\t\timportPath := fmt.Sprintf(\"webhook%s\", f.Resource.Version)\n\t\t\timports = append(imports, fmt.Sprintf(webhookImportCodeFragment, importPath, f.Repo, f.Resource.Version))\n\t\t} else {\n\t\t\timportPath := fmt.Sprintf(\"webhook%s\", f.Resource.ImportAlias())\n\t\t\timports = append(imports, fmt.Sprintf(multiGroupWebhookImportCodeFragment, importPath,\n\t\t\t\tf.Repo, f.Resource.Group, f.Resource.Version))\n\t\t}\n\t}\n\n\tif f.WireController {\n\t\tif !f.MultiGroup || f.Resource.Group == \"\" {\n\t\t\timports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo))\n\t\t} else {\n\t\t\timports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment,\n\t\t\t\tf.Resource.PackageName(), f.Repo, f.Resource.Group))\n\t\t}\n\t}\n\n\t// Generate add scheme code fragments\n\taddScheme := make([]string, 0)\n\tif f.WireResource || f.Resource.IsExternal() {\n\t\taddScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))\n\t}\n\n\t// Generate setup code fragments\n\tsetup := make([]string, 0)\n\tif f.WireController {\n\t\tif !f.MultiGroup || f.Resource.Group == \"\" {\n\t\t\tsetup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment,\n\t\t\t\tf.Resource.Kind, f.Resource.Kind))\n\t\t} else {\n\t\t\tsetup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment,\n\t\t\t\tf.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind))\n\t\t}\n\t}\n\tif f.WireWebhook {\n\t\tif f.IsLegacyPath {\n\t\t\tsetup = append(setup, fmt.Sprintf(webhookSetupCodeFragmentLegacy,\n\t\t\t\tf.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))\n\t\t} else {\n\t\t\tif !f.MultiGroup || f.Resource.Group == \"\" {\n\t\t\t\tsetup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,\n\t\t\t\t\t\"webhook\"+f.Resource.Version, f.Resource.Kind, f.Resource.Kind))\n\t\t\t} else {\n\t\t\t\tsetup = append(setup, fmt.Sprintf(webhookSetupCodeFragment,\n\t\t\t\t\t\"webhook\"+f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind))\n\t\t\t}\n\t\t}\n\t}\n\n\t// Only store code fragments in the map if the slices are non-empty\n\tif len(imports) != 0 {\n\t\tfragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports\n\t}\n\tif len(addScheme) != 0 {\n\t\tfragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme\n\t}\n\tif len(setup) != 0 {\n\t\tfragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup\n\t}\n\n\treturn fragments\n}\n\n//nolint:lll\nvar mainTemplate = `{{ .Boilerplate }}\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n{{- if .Namespaced }}\n\t\"fmt\"\n{{- end }}\n\t\"os\"\n{{- if .Namespaced }}\n\t\"strings\"\n{{- end }}\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n{{- if .Namespaced }}\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n{{- end }}\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t%s\n)\n\nvar (\n\tscheme = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\t%s\n}\n{{- if .Namespaced }}\n\n// getWatchNamespace returns the namespace(s) the manager should watch for changes.\n// It reads the value from the WATCH_NAMESPACE environment variable.\n// - If WATCH_NAMESPACE is not set, an error is returned\n// - If WATCH_NAMESPACE contains a single namespace, the manager watches that namespace\n// - If WATCH_NAMESPACE contains comma-separated namespaces, the manager watches those namespaces\nfunc getWatchNamespace() (string, error) {\n\twatchNamespaceEnvVar := \"WATCH_NAMESPACE\"\n\tns, found := os.LookupEnv(watchNamespaceEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"%%s must be set\", watchNamespaceEnvVar)\n\t}\n\treturn ns, nil\n}\n\n// setupCacheNamespaces configures the cache to watch specific namespace(s).\n// It supports both single namespace (\"ns1\") and multi-namespace (\"ns1,ns2,ns3\") formats.\nfunc setupCacheNamespaces(namespaces string) cache.Options {\n\tdefaultNamespaces := make(map[string]cache.Config)\n\tfor ns := range strings.SplitSeq(namespaces, \",\") {\n\t\tdefaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}\n\t}\n\treturn cache.Options{\n\t\tDefaultNamespaces: defaultNamespaces,\n\t}\n}\n{{- end }}\n\n// nolint:gocyclo\nfunc main() {\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \" +\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \" +\n\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts: tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n{{- if .Namespaced }}\n\n\t// Get the namespace(s) for namespace-scoped mode from WATCH_NAMESPACE environment variable.\n\t// The manager will only watch and manage resources in the specified namespace(s).\n\twatchNamespace, err := getWatchNamespace()\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Unable to get WATCH_NAMESPACE, \"+\n\t\t\t\"the manager will watch and manage resources in all namespaces\")\n\t\tos.Exit(1)\n\t}\n\n\t// Configure manager options for namespace-scoped mode\n\tmgrOptions := ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\t{{- if not .Domain }}\n\t\tLeaderElectionID:       \"{{ hashFNV .Repo }}\",\n\t\t{{- else }}\n\t\tLeaderElectionID:       \"{{ hashFNV .Repo }}.{{ .Domain }}\",\n\t\t{{- end }}\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t}\n\n\t// Configure cache to watch namespace(s) specified in WATCH_NAMESPACE\n\tmgrOptions.Cache = setupCacheNamespaces(watchNamespace)\n\tsetupLog.Info(\"Watching namespace(s)\", \"namespaces\", watchNamespace)\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)\n{{- else }}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme: scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\t{{- if not .Domain }}\n\t\tLeaderElectionID:        \"{{ hashFNV .Repo }}\",\n\t\t{{- else }}\n\t\tLeaderElectionID:        \"{{ hashFNV .Repo }}.{{ .Domain }}\",\n\t\t{{- end }}\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n{{- end }}\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\t%s\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Controller{}\n\n// Controller scaffolds the file that defines the controller for a CRD or a builtin resource\n//\n\ntype Controller struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.NamespacedMixin\n\n\tControllerRuntimeVersion string\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Controller) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[group]\", \"%[kind]_controller.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[kind]_controller.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = controllerTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.Error\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst controllerTemplate = `{{ .Boilerplate }}\n\npackage {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controller{{ end }}\n\nimport (\n\t\"context\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n)\n\n// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object\ntype {{ .Resource.Kind }}Reconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n{{ if .Namespaced -}}\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }}/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},namespace={{ .ProjectName }}-system,resources={{ .Resource.Plural }}/finalizers,verbs=update\n{{- else -}}\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/finalizers,verbs=update\n{{- end }}\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the {{ .Resource.Kind }} object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@{{ .ControllerRuntimeVersion }}/pkg/reconcile\nfunc (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t\tFor(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).\n\t\t{{- else -}}\n\t\t// Uncomment the following line adding a pointer to an instance of the controlled resource as an argument\n\t\t// For().\n\t\t{{- end }}\n\t\t{{- if and (.MultiGroup) (not (isEmptyStr .Resource.Group)) }}\n\t\tNamed(\"{{ lower .Resource.Group }}-{{ lower .Resource.Kind }}\").\n\t\t{{- else }}\n\t\tNamed(\"{{ lower .Resource.Kind }}\").\n\t\t{{- end }}\n\t\tComplete(r)\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar (\n\t_ machinery.Template = &SuiteTest{}\n\t_ machinery.Inserter = &SuiteTest{}\n)\n\n// SuiteTest scaffolds the file that sets up the controller tests\n//\n\ntype SuiteTest struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\t// CRDDirectoryRelativePath define the Path for the CRD\n\tCRDDirectoryRelativePath string\n\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *SuiteTest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[group]\", \"suite_test.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"suite_test.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate,\n\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t)\n\n\t// If is multigroup the path needs to be ../../ since it has\n\t// the group dir.\n\tf.CRDDirectoryRelativePath = `\"..\",\"..\"`\n\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\tf.CRDDirectoryRelativePath = `\"..\", \"..\",\"..\"`\n\t}\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t}\n\n\treturn nil\n}\n\nconst (\n\timportMarker    = \"imports\"\n\taddSchemeMarker = \"scheme\"\n)\n\n// GetMarkers implements file.Inserter\nfunc (f *SuiteTest) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t}\n}\n\nconst (\n\tapiImportCodeFragment = `%s \"%s\"\n`\n\taddschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme)\nExpect(err).NotTo(HaveOccurred())\n\n`\n)\n\n// GetCodeFragments implements file.Inserter\nfunc (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap {\n\tfragments := make(machinery.CodeFragmentsMap, 2)\n\n\t// Generate import code fragments\n\timports := make([]string, 0)\n\tif f.Resource.Path != \"\" {\n\t\timports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))\n\t}\n\n\t// Generate add scheme code fragments\n\taddScheme := make([]string, 0)\n\tif f.Resource.Path != \"\" {\n\t\taddScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias()))\n\t}\n\n\t// Only store code fragments in the map if the slices are non-empty\n\tif len(imports) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports\n\t}\n\tif len(addScheme) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme\n\t}\n\n\treturn fragments\n}\n\nconst controllerSuiteTestTemplate = `{{ .Boilerplate }}\n\n{{if and .MultiGroup .Resource.Group }}\npackage {{ .Resource.PackageName }}\n{{else}}\npackage controller\n{{end}}\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t%s\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx context.Context\n\tcancel context.CancelFunc\n\ttestEnv *envtest.Environment\n\tcfg *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\t%s\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join({{ .CRDDirectoryRelativePath }}, \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: {{ .Resource.HasAPI }},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join({{ .CRDDirectoryRelativePath }}, \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_test_template.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ControllerTest{}\n\n// ControllerTest scaffolds the file that sets up the controller unit tests\n//\n\ntype ControllerTest struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\tForce bool\n\n\tDoAPI bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ControllerTest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[group]\", \"%[kind]_controller_test.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(\"internal\", \"controller\", \"%[kind]_controller_test.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tf.TemplateBody = controllerTestTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t}\n\n\treturn nil\n}\n\nconst controllerTestTemplate = `{{ .Boilerplate }}\n\n{{if and .MultiGroup .Resource.Group }}\npackage {{ .Resource.PackageName }}\n{{else}}\npackage controller\n{{end}}\n\nimport (\n\t{{ if .DoAPI -}}\n\t\"context\"\n\t{{- end }}\n\t. \"github.com/onsi/ginkgo/v2\"\n\t{{ if .DoAPI -}}\n\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n\t{{- end }}\n)\n\nvar _ = Describe(\"{{ .Resource.Kind }} Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\t{{ if .DoAPI -}}\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\",  // TODO(user):Modify as needed\n\t\t}\n\t\t{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind {{ .Resource.Kind }}\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance {{ .Resource.Kind }}\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\t{{- end }}\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\t{{ if .DoAPI -}}\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &{{ .Resource.Kind }}Reconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t{{- end }}\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/customgcl.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CustomGcl{}\n\n// CustomGcl scaffolds the .custom-gcl.yml file for golangci-lint module plugins\ntype CustomGcl struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// GolangciLintVersion is the version of golangci-lint to use\n\tGolangciLintVersion string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CustomGcl) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".custom-gcl.yml\"\n\t}\n\n\tf.TemplateBody = customGclTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst customGclTemplate = `# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: {{ .GolangciLintVersion }}\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/devcontainer.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// devContainerTemplate defines the devcontainer.json configuration\n// Works with VS Code, GitHub Codespaces, and other devcontainer-compatible tools\n//\n// Configuration choices:\n//   - moby: false - Uses Docker CE instead of Moby, fixes DinD issues in Codespaces\n//   - dockerDefaultAddressPool - Prevents subnet conflicts in shared/cloud environments\n//   - --privileged - Required for Docker daemon to run inside container (DinD)\n//   - --init - Properly handles zombie processes and signal forwarding\n//   - GO111MODULE=on - Ensures Go modules work consistently\n//   - Runs as root (golang:1.25 default) - no sudo needed in post-install script\nconst devContainerTemplate = `{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n`\n\nconst postInstallScript = `#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n`\n\nvar (\n\t_ machinery.Template = &DevContainer{}\n\t_ machinery.Template = &DevContainerPostInstallScript{}\n)\n\n// DevContainer scaffoldds a `devcontainer.json` configurations file for creating Kubebuilder & Kind based DevContainer.\ntype DevContainer struct {\n\tmachinery.TemplateMixin\n}\n\n// DevContainerPostInstallScript defines the scaffold that will be done with the post install script\ntype DevContainerPostInstallScript struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults set defaults for this template\nfunc (f *DevContainer) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".devcontainer/devcontainer.json\"\n\t}\n\n\tf.TemplateBody = devContainerTemplate\n\n\treturn nil\n}\n\n// SetTemplateDefaults set the defaults of this template\nfunc (f *DevContainerPostInstallScript) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".devcontainer/post-install.sh\"\n\t}\n\n\tf.TemplateBody = postInstallScript\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Dockerfile{}\n\n// Dockerfile scaffolds a file that defines the containerized build process\ntype Dockerfile struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Dockerfile) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"Dockerfile\"\n\t}\n\n\tf.TemplateBody = dockerfileTemplate\n\n\treturn nil\n}\n\nconst dockerfileTemplate = `# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &DockerIgnore{}\n\n// DockerIgnore scaffolds a file that defines which files should be ignored by the containerized build process\ntype DockerIgnore struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *DockerIgnore) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".dockerignore\"\n\t}\n\n\tf.TemplateBody = dockerignorefileTemplate\n\n\treturn nil\n}\n\nconst dockerignorefileTemplate = `# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/github/lint.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &TestCi{}\n\n// LintCi scaffolds the GitHub Action to lint the project\ntype LintCi struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\n\t// golangci-lint version to use in the project\n\tGolangciLintVersion string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *LintCi) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"lint.yml\")\n\t}\n\n\tf.TemplateBody = lintCiTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst lintCiTemplate = `name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/github/test-e2e.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &E2eTestCi{}\n\n// E2eTestCi scaffolds the GitHub Action to call make test-e2e\ntype E2eTestCi struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *E2eTestCi) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"test-e2e.yml\")\n\t}\n\n\tf.TemplateBody = e2eTestCiTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst e2eTestCiTemplate = `name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/github/test.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &TestCi{}\n\n// TestCi scaffolds the GitHub Action to call make test\ntype TestCi struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *TestCi) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"test.yml\")\n\t}\n\n\tf.TemplateBody = testCiTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst testCiTemplate = `name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &GitIgnore{}\n\n// GitIgnore scaffolds a file that defines which files should be ignored by git\ntype GitIgnore struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *GitIgnore) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".gitignore\"\n\t}\n\n\tf.TemplateBody = gitignoreTemplate\n\n\treturn nil\n}\n\nconst gitignoreTemplate = `# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with ` + \"`go test -c`\" + `\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/golangci.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Golangci{}\n\n// Golangci scaffolds a file which define Golangci rules\ntype Golangci struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Golangci) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \".golangci.yml\"\n\t}\n\n\tf.TemplateBody = golangciTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst golangciTemplate = `version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &GoMod{}\n\n// GoMod scaffolds a file that defines the project dependencies\ntype GoMod struct {\n\tmachinery.TemplateMixin\n\tmachinery.RepositoryMixin\n\n\tControllerRuntimeVersion string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *GoMod) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"go.mod\"\n\t}\n\n\tf.TemplateBody = goModTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst goModTemplate = `module {{ .Repo }}\n\ngo 1.25.3\n\nrequire (\n\tsigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }}\n)\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage hack\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// DefaultBoilerplatePath is the default path to the boilerplate file\nvar DefaultBoilerplatePath = filepath.Join(\"hack\", \"boilerplate.go.txt\")\n\nvar _ machinery.Template = &Boilerplate{}\n\n// Boilerplate scaffolds a file that defines the common header for the rest of the files\ntype Boilerplate struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\n\t// License is the License type to write\n\tLicense string\n\n\t// Licenses maps License types to their actual string\n\tLicenses map[string]string\n\n\t// Owner is the copyright owner - e.g. \"The Kubernetes Authors\"\n\tOwner string\n\n\t// Year is the copyright year\n\tYear string\n}\n\n// Validate implements file.RequiresValidation\nfunc (f *Boilerplate) Validate() error {\n\tif f.License != \"\" {\n\t\tif _, foundKnown := knownLicenses[f.License]; !foundKnown {\n\t\t\tif _, found := f.Licenses[f.License]; !found {\n\t\t\t\treturn fmt.Errorf(\"unknown specified license %s\", f.License)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Boilerplate) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = DefaultBoilerplatePath\n\t}\n\n\tif f.License == \"\" {\n\t\tf.License = \"apache2\"\n\t}\n\n\tif f.Licenses == nil {\n\t\tf.Licenses = make(map[string]string, len(knownLicenses))\n\t}\n\n\tfor key, value := range knownLicenses {\n\t\tif _, hasLicense := f.Licenses[key]; !hasLicense {\n\t\t\tf.Licenses[key] = value\n\t\t}\n\t}\n\n\tif f.Year == \"\" {\n\t\tf.Year = fmt.Sprintf(\"%v\", time.Now().Year())\n\t}\n\n\t// Boilerplate given\n\tif len(f.Boilerplate) > 0 {\n\t\tf.TemplateBody = f.Boilerplate\n\t\treturn nil\n\t}\n\n\tf.TemplateBody = boilerplateTemplate\n\n\treturn nil\n}\n\nconst boilerplateTemplate = `/*\n{{ if .Owner -}}\nCopyright {{ .Year }} {{ .Owner }}.\n{{- else -}}\nCopyright {{ .Year }}.\n{{- end }}\n{{ index .Licenses .License }}*/`\n\nvar knownLicenses = map[string]string{\n\t\"apache2\":   apache2,\n\t\"copyright\": \"\",\n}\n\nconst apache2 = `\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Makefile{}\n\n// Makefile scaffolds a file that defines project management CLI commands\ntype Makefile struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// Image is controller manager image name\n\tImage string\n\t// BoilerplatePath is the path to the boilerplate file\n\tBoilerplatePath string\n\t// Controller tools version to use in the project\n\tControllerToolsVersion string\n\t// Kustomize version to use in the project\n\tKustomizeVersion string\n\t// golangci-lint version to use in the project\n\tGolangciLintVersion string\n\t// ControllerRuntimeVersion version to be used to download the envtest setup script\n\tControllerRuntimeVersion string\n\t// EnvtestVersion store the name of the verions to be used to install setup-envtest\n\tEnvtestVersion string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Makefile) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"Makefile\"\n\t}\n\n\tf.TemplateBody = makefileTemplate\n\n\tf.IfExistsAction = machinery.Error\n\n\tif f.Image == \"\" {\n\t\tf.Image = \"controller:latest\"\n\t}\n\n\t// TODO: Current workaround for setup-envtest compatibility\n\t// Due to past instances where controller-runtime maintainers released\n\t// versions without corresponding branches, directly relying on branches\n\t// poses a risk of breaking the Kubebuilder chain. Such practices may\n\t// change over time, potentially leading to compatibility issues. This\n\t// approach, although not ideal, remains the best solution for ensuring\n\t// compatibility with controller-runtime releases as of now. For more\n\t// details on the quest for a more robust solution, refer to the issue\n\t// raised in the controller-runtime repository: https://github.com/kubernetes-sigs/controller-runtime/issues/2744\n\tif f.EnvtestVersion == \"\" {\n\t\tf.EnvtestVersion = \"latest\"\n\t}\n\treturn nil\n}\n\n//nolint:lll\nconst makefileTemplate = `# Image URL to use all building/pushing image targets\nIMG ?= {{ .Image }}\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t{{ if .BoilerplatePath -}}\n\t\"$(CONTROLLER_GEN)\" object:headerFile={{printf \"%q\" .BoilerplatePath}} paths=\"./...\"\n\t{{- else -}}\n\t\"$(CONTROLLER_GEN)\" object paths=\"./...\"\n\t{{- end }}\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= {{ .ProjectName }}-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name {{ .ProjectName }}-builder\n\t$(CONTAINER_TOOL) buildx use {{ .ProjectName }}-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm {{ .ProjectName }}-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= {{ .KustomizeVersion }}\nCONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }}\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= {{ .GolangciLintVersion }}\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{\"{{\"}}if .Replace{{\"}}\"}}{{\"{{\"}}.Replace.Version{{\"}}\"}}{{\"{{\"}}else{{\"}}\"}}{{\"{{\"}}.Version{{\"}}\"}}{{\"{{\"}}end{{\"}}\"}}' $(1) 2>/dev/null)\nendef\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Readme{}\n\n// Readme scaffolds a README.md file\ntype Readme struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ProjectNameMixin\n\n\tLicense string\n\n\t// CommandName stores the name of the bin used\n\tCommandName string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Readme) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"README.md\"\n\t}\n\n\tf.License = strings.Replace(\n\t\tstrings.Replace(f.Boilerplate, \"/*\", \"\", 1),\n\t\t\"*/\", \"\", 1)\n\n\tf.TemplateBody = fmt.Sprintf(readmeFileTemplate,\n\t\tcodeFence(\"make docker-build docker-push IMG=<some-registry>/{{ .ProjectName }}:tag\"),\n\t\tcodeFence(\"make install\"),\n\t\tcodeFence(\"make deploy IMG=<some-registry>/{{ .ProjectName }}:tag\"),\n\t\tcodeFence(\"kubectl apply -k config/samples/\"),\n\t\tcodeFence(\"kubectl delete -k config/samples/\"),\n\t\tcodeFence(\"make uninstall\"),\n\t\tcodeFence(\"make undeploy\"),\n\t\tcodeFence(\"make build-installer IMG=<some-registry>/{{ .ProjectName }}:tag\"),\n\t\tcodeFence(\"kubectl apply -f https://raw.githubusercontent.com/<org>/{{ .ProjectName }}/\"+\n\t\t\t\"<tag or branch>/dist/install.yaml\"),\n\t\tcodeFence(fmt.Sprintf(\"%s edit --plugins=helm/v2-alpha\", f.CommandName)),\n\t)\n\n\treturn nil\n}\n\nconst readmeFileTemplate = `# {{ .ProjectName }}\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by ` + \"`IMG`\" + `:**\n\n%s\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n%s\n\n**Deploy the Manager to the cluster with the image specified by ` + \"`IMG`\" + `:**\n\n%s\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n%s\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n%s\n\n**Delete the APIs(CRDs) from the cluster:**\n\n%s\n\n**UnDeploy the controller from the cluster:**\n\n%s\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n%s\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n%s\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n%s\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run ` + \"`make help`\" + ` for more information on all potential ` + \"`make`\" + ` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n{{ .License }}\n`\n\nfunc codeFence(code string) string {\n\treturn \"```sh\" + \"\\n\" + code + \"\\n\" + \"```\"\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/suite.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &SuiteTest{}\n\n// SuiteTest scaffolds the files for the e2e tests\ntype SuiteTest struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.RepositoryMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *SuiteTest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"test/e2e/e2e_suite_test.go\"\n\t}\n\n\tf.TemplateBody = suiteTestTemplate\n\treturn nil\n}\n\nvar suiteTestTemplate = `//go:build e2e\n// +build e2e\n\n{{ .Boilerplate }}\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"{{ .Repo }}/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/{{ .ProjectName }}:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting {{ .ProjectName }} e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n})\n\nvar _ = AfterSuite(func() {\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e/test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar (\n\t_ machinery.Template = &Test{}\n\t_ machinery.Inserter = &WebhookTestUpdater{}\n)\n\nconst (\n\twebhookChecksMarker           = \"e2e-webhooks-checks\"\n\tmetricsWebhookReadinessMarker = \"e2e-metrics-webhooks-readiness\"\n)\n\n// Test defines the basic setup for the e2e test\ntype Test struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.RepositoryMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults set defaults for this template\nfunc (f *Test) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"test\", \"e2e\", \"e2e_test.go\")\n\t}\n\n\t// This is where the template body is defined with markers\n\tf.TemplateBody = testCodeTemplate\n\n\treturn nil\n}\n\n// WebhookTestUpdater updates e2e_test.go to insert additional webhook validation tests\ntype WebhookTestUpdater struct {\n\tmachinery.RepositoryMixin\n\tmachinery.ProjectNameMixin\n\tmachinery.ResourceMixin\n\tWireWebhook bool\n}\n\n// GetPath implements file.Builder\nfunc (*WebhookTestUpdater) GetPath() string {\n\treturn filepath.Join(\"test\", \"e2e\", \"e2e_test.go\")\n}\n\n// GetIfExistsAction implements file.Builder\nfunc (*WebhookTestUpdater) GetIfExistsAction() machinery.IfExistsAction {\n\treturn machinery.OverwriteFile // Ensures only the marker is replaced\n}\n\n// GetMarkers implements file.Inserter\nfunc (f *WebhookTestUpdater) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(f.GetPath(), webhookChecksMarker),\n\t\tmachinery.NewMarkerFor(f.GetPath(), metricsWebhookReadinessMarker),\n\t}\n}\n\n// GetCodeFragments implements file.Inserter\nfunc (f *WebhookTestUpdater) GetCodeFragments() machinery.CodeFragmentsMap {\n\t// Check if any webhook type exists (defaulting, validation, or conversion)\n\thasAnyWebhook := f.WireWebhook || (f.Resource != nil && f.Resource.HasConversionWebhook())\n\n\tif !hasAnyWebhook {\n\t\treturn nil\n\t}\n\n\tfilePath := f.GetPath()\n\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tlog.Warn(\"Unable to read file\", \"file\", filePath, \"error\", err)\n\t\tlog.Warn(\"Webhook test code injection will be skipped for this file.\")\n\t\tlog.Warn(\"This typically occurs when the file was removed and is missing.\")\n\t\tlog.Warn(\"If you intend to scaffold webhook tests, ensure the file and its markers exist.\")\n\t\treturn nil\n\t}\n\n\tcodeFragments := machinery.CodeFragmentsMap{}\n\tmarkers := f.GetMarkers()\n\n\tfor _, marker := range markers {\n\t\tmarkerStr := marker.String()\n\t\tif !bytes.Contains(content, []byte(markerStr)) {\n\t\t\tlog.Warn(\"Marker not found in file, skipping webhook test code injection\",\n\t\t\t\t\"marker\", markerStr,\n\t\t\t\t\"file_path\", filePath)\n\t\t\tcontinue // skip this marker\n\t\t}\n\n\t\tswitch {\n\t\tcase strings.Contains(markerStr, webhookChecksMarker):\n\t\t\tvar fragments []string\n\t\t\tfragments = append(fragments, webhookChecksFragment)\n\n\t\t\tif f.Resource != nil && f.Resource.HasDefaultingWebhook() {\n\t\t\t\tmutatingWebhookCode := fmt.Sprintf(mutatingWebhookChecksFragment, f.ProjectName)\n\t\t\t\tfragments = append(fragments, mutatingWebhookCode)\n\t\t\t}\n\n\t\t\tif f.Resource != nil && f.Resource.HasValidationWebhook() {\n\t\t\t\tvalidatingWebhookCode := fmt.Sprintf(validatingWebhookChecksFragment, f.ProjectName)\n\t\t\t\tfragments = append(fragments, validatingWebhookCode)\n\t\t\t}\n\n\t\t\tif f.Resource != nil && f.Resource.HasConversionWebhook() {\n\t\t\t\tconversionWebhookCode := fmt.Sprintf(\n\t\t\t\t\tconversionWebhookChecksFragment,\n\t\t\t\t\tf.Resource.Kind,\n\t\t\t\t\tf.Resource.Plural+\".\"+f.Resource.Group+\".\"+f.Resource.Domain,\n\t\t\t\t)\n\t\t\t\tfragments = append(fragments, conversionWebhookCode)\n\t\t\t}\n\n\t\t\tif len(fragments) > 0 {\n\t\t\t\tcodeFragments[marker] = fragments\n\t\t\t}\n\t\tcase strings.Contains(markerStr, metricsWebhookReadinessMarker):\n\t\t\t// Readiness checks only for defaulting/validation webhooks (they use webhook service)\n\t\t\t// Conversion webhooks don't need separate webhook service readiness checks\n\t\t\tif f.WireWebhook {\n\t\t\t\t// Skip if webhook readiness checks are already present\n\t\t\t\t// This prevents duplicate insertion when multiple webhooks are scaffolded\n\t\t\t\tif strings.Contains(string(content), \"waiting for the webhook service endpoints to be ready\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\twebhookServiceName := fmt.Sprintf(\"%s-webhook-service\", f.ProjectName)\n\t\t\t\tvar fragments []string\n\n\t\t\t\t// Add endpoint readiness check (applies to all webhook types)\n\t\t\t\tfragments = append(fragments, fmt.Sprintf(webhookEndpointsReadinessFragment, webhookServiceName))\n\n\t\t\t\t// Add mutating webhook configuration check if defaulting webhooks exist\n\t\t\t\tif f.Resource != nil && f.Resource.HasDefaultingWebhook() {\n\t\t\t\t\tfragments = append(fragments, fmt.Sprintf(mutatingWebhookReadinessFragment, f.ProjectName))\n\t\t\t\t}\n\n\t\t\t\t// Add validating webhook configuration check if validation webhooks exist\n\t\t\t\tif f.Resource != nil && f.Resource.HasValidationWebhook() {\n\t\t\t\t\tfragments = append(fragments, fmt.Sprintf(validatingWebhookReadinessFragment, f.ProjectName))\n\t\t\t\t}\n\n\t\t\t\t// Add stabilization wait at the end\n\t\t\t\tfragments = append(fragments, webhookStabilizationFragment)\n\n\t\t\t\tif len(fragments) > 0 {\n\t\t\t\t\tcodeFragments[marker] = fragments\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(codeFragments) == 0 {\n\t\treturn nil\n\t}\n\n\treturn codeFragments\n}\n\nconst webhookChecksFragment = `It(\"should provisioned cert-manager\", func() {\n\tBy(\"validating that cert-manager has the certificate Secret\")\n\tverifyCertManager := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t}\n\tEventually(verifyCertManager).Should(Succeed())\n})\n\n`\n\nconst mutatingWebhookChecksFragment = `It(\"should have CA injection for mutating webhooks\", func() {\n\tBy(\"checking CA injection for mutating webhooks\")\n\tverifyCAInjection := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\"%s-mutating-webhook-configuration\",\n\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\tmwhOutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\t}\n\tEventually(verifyCAInjection).Should(Succeed())\n})\n\n`\n\nconst validatingWebhookChecksFragment = `It(\"should have CA injection for validating webhooks\", func() {\n\tBy(\"checking CA injection for validating webhooks\")\n\tverifyCAInjection := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\"%s-validating-webhook-configuration\",\n\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\tvwhOutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t}\n\tEventually(verifyCAInjection).Should(Succeed())\n})\n\n`\n\nconst conversionWebhookChecksFragment = `It(\"should have CA injection for %[1]s conversion webhook\", func() {\n\tBy(\"checking CA injection for %[1]s conversion webhook\")\n\tverifyCAInjection := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\"customresourcedefinitions.apiextensions.k8s.io\",\n\t\t\t\"%[2]s\",\n\t\t\t\"-o\", \"go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}\")\n\t\tvwhOutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t}\n\tEventually(verifyCAInjection).Should(Succeed())\n})\n\n`\n\nconst webhookEndpointsReadinessFragment = `By(\"waiting for the webhook service endpoints to be ready\")\n\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\"-l\", \"kubernetes.io/service-name=%s\",\n\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\toutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t}\n\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n`\n\nconst mutatingWebhookReadinessFragment = `By(\"verifying the mutating webhook server is ready\")\n\tverifyMutatingWebhookReady := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\", \"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\"%s-mutating-webhook-configuration\",\n\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\toutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred(), \"MutatingWebhookConfiguration should exist\")\n\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Mutating webhook CA bundle not yet injected\")\n\t}\n\tEventually(verifyMutatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n`\n\nconst validatingWebhookReadinessFragment = `By(\"verifying the validating webhook server is ready\")\n\tverifyValidatingWebhookReady := func(g Gomega) {\n\t\tcmd := exec.Command(\"kubectl\", \"get\", \"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\"%s-validating-webhook-configuration\",\n\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\toutput, err := utils.Run(cmd)\n\t\tg.Expect(err).NotTo(HaveOccurred(), \"ValidatingWebhookConfiguration should exist\")\n\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Validating webhook CA bundle not yet injected\")\n\t}\n\tEventually(verifyValidatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n`\n\nconst webhookStabilizationFragment = `By(\"waiting additional time for webhook server to stabilize\")\n\ttime.Sleep(5 * time.Second)\n\n`\n\nvar testCodeTemplate = `//go:build e2e\n// +build e2e\n\n{{ .Boilerplate }}\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"{{ .Repo }}/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"{{ .ProjectName }}-system\"\n// serviceAccountName created for the project\nconst serviceAccountName = \"{{ .ProjectName }}-controller-manager\"\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"{{ .ProjectName }}-controller-manager-metrics-service\"\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"{{ .ProjectName }}-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{\"{{\"}} range .items {{\"}}\"}}\" +\n\t\t\t\t\t\"{{\"{{\"}} if not .metadata.deletionTimestamp {{\"}}\"}}\" +\n\t\t\t\t\t\"{{\"{{\"}} .metadata.name {{\"}}\"}}\"+\n\t\t\t\t\t\"{{\"{{\"}} \\\"\\\\n\\\" {{\"}}\"}}{{\"{{\"}} end {{\"}}\"}}{{\"{{\"}} end {{\"}}\"}}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole={{ .ProjectName}}-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n \t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(` + \"`\" + `{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do ` +\n\t`curl -v -k -H 'Authorization: Bearer %s' ` +\n\t`https://%s.%s.svc.cluster.local:8443/metrics ` +\n\t`&& exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}` + \"`\" + `, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5 * time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(` + \"`controller_runtime_reconcile_total{controller=\\\"%s\\\",result=\\\"success\\\"} 1`\" + `,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = ` + \"`\" + `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}` + \"`\" + `\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string ` + \"`json:\\\"token\\\"`\" + `\n\t} ` + \"`json:\\\"status\\\"`\" + `\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/test/utils/utils.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Utils{}\n\n// Utils define the template for the utils file\ntype Utils struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults set the defaults for its template\nfunc (f *Utils) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = \"test/utils/utils.go\"\n\t}\n\n\tf.TemplateBody = utilsTemplate\n\n\treturn nil\n}\n\nvar utilsTemplate = `{{ .Boilerplate }}\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\t\n\tdefaultKindBinary = \"kind\"\n\tdefaultKindCluster = \"kind\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhooks\n\nimport (\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Webhook{}\n\n// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource\ntype Webhook struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\t// Is the Group domain for the Resource replacing '.' with '-'\n\tQualifiedGroupWithDash string\n\n\t// Define value for AdmissionReviewVersions marker\n\tAdmissionReviewVersions string\n\n\tForce bool\n\n\t// Deprecated - The flag should be removed from go/v5\n\t// IsLegacyPath indicates if webhooks should be scaffolded under the API.\n\t// Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.\n\t// This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.\n\tIsLegacyPath bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *Webhook) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\t// Deprecated: Remove me when remove go/v4\n\t\tbaseDir := \"api\"\n\t\tif !f.IsLegacyPath {\n\t\t\tbaseDir = filepath.Join(\"internal\", \"webhook\")\n\t\t}\n\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(baseDir, \"%[group]\", \"%[version]\", \"%[kind]_webhook.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(baseDir, \"%[version]\", \"%[kind]_webhook.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\twebhookTemplate := webhookTemplate\n\tif f.Resource.HasDefaultingWebhook() {\n\t\twebhookTemplate = webhookTemplate + defaultingWebhookTemplate\n\t}\n\tif f.Resource.HasValidationWebhook() {\n\t\twebhookTemplate = webhookTemplate + validatingWebhookTemplate\n\t}\n\tf.TemplateBody = webhookTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.Error\n\t}\n\n\tf.AdmissionReviewVersions = \"v1\"\n\tf.QualifiedGroupWithDash = strings.ReplaceAll(f.Resource.QualifiedGroup(), \".\", \"-\")\n\n\treturn nil\n}\n\nconst (\n\twebhookTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\t{{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }}\n\t\"context\"\n\t{{- end }}\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t{{- if .Resource.HasValidationWebhook }}\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\t{{- end }}\n\t{{ if not .IsLegacyPath -}}\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n\t{{- end }}\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar {{ lower .Resource.Kind }}log = logf.Log.WithName(\"{{ lower .Resource.Kind }}-resource\")\n\n{{- if .IsLegacyPath -}}\n// SetupWebhookWithManager will setup the manager to manage the webhooks.\nfunc (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, r).\n\t\t{{- if .Resource.HasValidationWebhook }}\n\t\tWithValidator(&{{ .Resource.Kind }}CustomValidator{}).\n\t\t{{- if ne .Resource.Webhooks.ValidationPath \"\" }}\n\t\tWithValidatorCustomPath(\"{{ .Resource.Webhooks.ValidationPath }}\").\n\t\t{{- end }}\n\t\t{{- end }}\n\t\t{{- if .Resource.HasDefaultingWebhook }}\n\t\tWithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}).\n\t\t{{- if ne .Resource.Webhooks.DefaultingPath \"\" }}\n\t\tWithDefaulterCustomPath(\"{{ .Resource.Webhooks.DefaultingPath }}\").\n\t\t{{- end }}\n\t\t{{- end }}\n\t\tComplete()\n}\n{{- else }}\n// Setup{{ .Resource.Kind }}WebhookWithManager registers the webhook for {{ .Resource.Kind }} in the manager.\nfunc Setup{{ .Resource.Kind }}WebhookWithManager(mgr ctrl.Manager) error {\n\t{{- if not (isEmptyStr .Resource.ImportAlias) }}\n\treturn ctrl.NewWebhookManagedBy(mgr, &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}).\n\t{{- else }}\n\treturn ctrl.NewWebhookManagedBy(mgr, &{{ .Resource.Kind }}{}).\n\t{{- end }}\n\t\t{{- if .Resource.HasValidationWebhook }}\n\t\tWithValidator(&{{ .Resource.Kind }}CustomValidator{}).\n\t\t{{- if ne .Resource.Webhooks.ValidationPath \"\" }}\n\t\tWithValidatorCustomPath(\"{{ .Resource.Webhooks.ValidationPath }}\").\n\t\t{{- end }}\n\t\t{{- end }}\n\t\t{{- if .Resource.HasDefaultingWebhook }}\n\t\tWithDefaulter(&{{ .Resource.Kind }}CustomDefaulter{}).\n\t\t{{- if ne .Resource.Webhooks.DefaultingPath \"\" }}\n\t\tWithDefaulterCustomPath(\"{{ .Resource.Webhooks.DefaultingPath }}\").\n\t\t{{- end }}\n\t\t{{- end }}\n\t\tComplete()\n}\n{{- end }}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n`\n\n\t//nolint:lll\n\tdefaultingWebhookTemplate = `\n// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion \"v1\" }}webhookVersions={{\"{\"}}{{ .Resource.Webhooks.WebhookVersion }}{{\"}\"}},{{ end }}{{- if ne .Resource.Webhooks.DefaultingPath \"\" -}}path={{ .Resource.Webhooks.DefaultingPath }}{{- else -}}path=/mutate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup \"core\") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{- end -}},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup \"core\") }}\"\"{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}\n\n{{ if .IsLegacyPath -}}\n// +kubebuilder:object:generate=false\n{{- end }}\n// {{ .Resource.Kind }}CustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind {{ .Resource.Kind }} when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype {{ .Resource.Kind }}CustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind {{ .Resource.Kind }}.\n{{- if .IsLegacyPath }}\nfunc (d *{{ .Resource.Kind }}CustomDefaulter) Default(_ context.Context, obj *{{ .Resource.Kind }}) error {\n\t{{ lower .Resource.Kind }}log.Info(\"Defaulting for {{ .Resource.Kind }}\", \"name\", obj.GetName())\n{{- else }}\nfunc (d *{{ .Resource.Kind }}CustomDefaulter) Default(_ context.Context, obj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) error {\n\t{{ lower .Resource.Kind }}log.Info(\"Defaulting for {{ .Resource.Kind }}\", \"name\", obj.GetName())\n{{- end }}\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n`\n\n\t//nolint:lll\n\tvalidatingWebhookTemplate = `\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion \"v1\" }}webhookVersions={{\"{\"}}{{ .Resource.Webhooks.WebhookVersion }}{{\"}\"}},{{ end }}{{- if ne .Resource.Webhooks.ValidationPath \"\" -}}path={{ .Resource.Webhooks.ValidationPath }}{{- else -}}path=/validate-{{ if and .Resource.Core (eq .Resource.QualifiedGroup \"core\") }}-{{ else }}{{ .QualifiedGroupWithDash }}-{{ end }}{{ .Resource.Version }}-{{ lower .Resource.Kind }}{{- end -}},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ if and .Resource.Core (eq .Resource.QualifiedGroup \"core\") }}\"\"{{ else }}{{ .Resource.QualifiedGroup }}{{ end }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}-{{ .Resource.Version }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }}\n\n{{ if .IsLegacyPath -}}\n// +kubebuilder:object:generate=false\n{{- end }}\n// {{ .Resource.Kind }}CustomValidator struct is responsible for validating the {{ .Resource.Kind }} resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype {{ .Resource.Kind }}CustomValidator struct{\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.\n{{- if .IsLegacyPath }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(_ context.Context, obj *{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon creation\", \"name\", obj.GetName())\n{{- else }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateCreate(_ context.Context, obj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon creation\", \"name\", obj.GetName())\n{{- end }}\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.\n{{- if .IsLegacyPath }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon update\", \"name\", newObj.GetName())\n{{- else }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon update\", \"name\", newObj.GetName())\n{{- end }}\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type {{ .Resource.Kind }}.\n{{- if .IsLegacyPath }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateDelete(_ context.Context, obj *{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon deletion\", \"name\", obj.GetName())\n{{- else }}\nfunc (v *{{ .Resource.Kind }}CustomValidator) ValidateDelete(_ context.Context, obj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) (admission.Warnings, error) {\n\t{{ lower .Resource.Kind }}log.Info(\"Validation for {{ .Resource.Kind }} upon deletion\", \"name\", obj.GetName())\n{{- end }}\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n`\n)\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_suitetest.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhooks\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar (\n\t_ machinery.Template = &WebhookSuite{}\n\t_ machinery.Inserter = &WebhookSuite{}\n)\n\n// WebhookSuite scaffolds the file that sets up the webhook tests\ntype WebhookSuite struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\t// todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed\n\tWireResource bool\n\n\t// K8SVersion define the k8s version used to do the scaffold\n\t// so that is possible retrieve the binaries\n\tK8SVersion string\n\n\t// BaseDirectoryRelativePath define the Path for the base directory when it is multigroup\n\tBaseDirectoryRelativePath string\n\n\t// Deprecated - The flag should be removed from go/v5\n\t// IsLegacyPath indicates if webhooks should be scaffolded under the API.\n\t// Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.\n\t// This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.\n\tIsLegacyPath bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *WebhookSuite) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\t// Deprecated: Remove me when remove go/v4\n\t\tbaseDir := \"api\"\n\t\tif !f.IsLegacyPath {\n\t\t\tbaseDir = filepath.Join(\"internal\", \"webhook\")\n\t\t}\n\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(baseDir, \"%[group]\", \"%[version]\", \"webhook_suite_test.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(baseDir, \"%[version]\", \"webhook_suite_test.go\")\n\t\t}\n\t}\n\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\tif f.IsLegacyPath {\n\t\tf.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplateLegacy,\n\t\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\t\tadmissionImportAlias,\n\t\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t\t\tmachinery.NewMarkerFor(f.Path, addWebhookManagerMarker),\n\t\t\t\"%s\",\n\t\t\t\"%d\",\n\t\t)\n\t} else {\n\t\tf.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate,\n\t\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\t\tf.Resource.ImportAlias(),\n\t\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t\t\tmachinery.NewMarkerFor(f.Path, addWebhookManagerMarker),\n\t\t\t\"%s\",\n\t\t\t\"%d\",\n\t\t)\n\t}\n\n\tif f.IsLegacyPath {\n\t\t// If is multigroup the path needs to be ../../../ since it has the group dir.\n\t\tf.BaseDirectoryRelativePath = `\"..\", \"..\"`\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.BaseDirectoryRelativePath = `\"..\", \"..\", \"..\"`\n\t\t}\n\t} else {\n\t\t// If is multigroup the path needs to be ../../../../ since it has the group dir.\n\t\tf.BaseDirectoryRelativePath = `\"..\", \"..\", \"..\"`\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.BaseDirectoryRelativePath = `\"..\", \"..\", \"..\", \"..\"`\n\t\t}\n\t}\n\n\treturn nil\n}\n\nconst (\n\tadmissionImportAlias    = \"admissionv1\"\n\tadmissionPath           = \"k8s.io/api/admission/v1\"\n\timportMarker            = \"imports\"\n\taddWebhookManagerMarker = \"webhook\"\n\taddSchemeMarker         = \"scheme\"\n)\n\n// GetMarkers implements file.Inserter\nfunc (f *WebhookSuite) GetMarkers() []machinery.Marker {\n\treturn []machinery.Marker{\n\t\tmachinery.NewMarkerFor(f.Path, importMarker),\n\t\tmachinery.NewMarkerFor(f.Path, addSchemeMarker),\n\t\tmachinery.NewMarkerFor(f.Path, addWebhookManagerMarker),\n\t}\n}\n\nconst (\n\tapiImportCodeFragment = `%s \"%s\"\n`\n\n\t// Deprecated - TODO: remove for go/v5\n\t// addWebhookManagerCodeFragmentLegacy is for the path under API\n\taddWebhookManagerCodeFragmentLegacy = `err = (&%s{}).SetupWebhookWithManager(mgr)\nExpect(err).NotTo(HaveOccurred())\n\n`\n\n\taddWebhookManagerCodeFragment = `err = Setup%sWebhookWithManager(mgr)\nExpect(err).NotTo(HaveOccurred())\n\n`\n)\n\n// GetCodeFragments implements file.Inserter\nfunc (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap {\n\tfragments := make(machinery.CodeFragmentsMap, 3)\n\n\t// Generate import code fragments\n\timports := make([]string, 0)\n\n\t// Generate add scheme code fragments\n\taddScheme := make([]string, 0)\n\n\t// Generate add webhookManager code fragments\n\taddWebhookManager := make([]string, 0)\n\tif f.IsLegacyPath {\n\t\timports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath))\n\t\taddWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragmentLegacy, f.Resource.Kind))\n\t} else {\n\t\timports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path))\n\t\taddWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind))\n\t}\n\n\t// Only store code fragments in the map if the slices are non-empty\n\tif len(addWebhookManager) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager\n\t}\n\tif len(imports) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports\n\t}\n\tif len(addScheme) != 0 {\n\t\tfragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme\n\t}\n\n\treturn fragments\n}\n\nconst webhookTestSuiteTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t%s\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx context.Context\n\tcancel context.CancelFunc\n\tk8sClient client.Client\n\tcfg *rest.Config\n\ttestEnv *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = %s.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t%s\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join({{ .BaseDirectoryRelativePath }}, \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: {{ .WireResource }},\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme:             scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:               webhookInstallOptions.LocalServingHost,\n\t\t\tPort:               webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir:            webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection:     false,\n\t\tMetrics:            metricsserver.Options{BindAddress: \"0\"},\n\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\t%s\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%s\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close();\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are \n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join({{ .BaseDirectoryRelativePath }}, \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n`\n\nconst webhookTestSuiteTemplateLegacy = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\t\"runtime\"\n\n    . \"github.com/onsi/ginkgo/v2\"\n    . \"github.com/onsi/gomega\"\n\t%s\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tapimachineryruntime \"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tcancel context.CancelFunc\n\tcfg *rest.Config\n\tctx context.Context\n\tk8sClient client.Client\n\ttestEnv *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join({{ .BaseDirectoryRelativePath }}, \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: {{ .WireResource }},\n\n\t\t// The BinaryAssetsDirectory is only required if you want to run the tests directly\n\t\t// without call the makefile target test. If not informed it will look for the\n\t\t// default path defined in controller-runtime which is /usr/local/kubebuilder/.\n\t\t// Note that you must have the required binaries setup under the bin directory to perform\n\t\t// the tests directly. When we run make test it will be setup and used automatically.\n\t\tBinaryAssetsDirectory: filepath.Join({{ .BaseDirectoryRelativePath }}, \"bin\", \"k8s\",\n\t\t\tfmt.Sprintf(\"{{ .K8SVersion }}-%%s-%%s\", runtime.GOOS, runtime.GOARCH)),\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\tvar err error\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tscheme := apimachineryruntime.NewScheme()\n\terr = AddToScheme(scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = %s.AddToScheme(scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t%s\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme:             scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:               webhookInstallOptions.LocalServingHost,\n\t\t\tPort:               webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir:            webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection:     false,\n\t\tMetrics:            metricsserver.Options{BindAddress: \"0\"},\n\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\t%s\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%s\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close();\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_template.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhooks\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &WebhookTest{}\n\n// WebhookTest scaffolds the file that sets up the webhook unit tests\ntype WebhookTest struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\tmachinery.IfNotExistsActionMixin\n\n\tForce bool\n\n\t// Deprecated - The flag should be removed from go/v5\n\t// IsLegacyPath indicates if webhooks should be scaffolded under the API.\n\t// Webhooks are now decoupled from APIs based on controller-runtime updates and community feedback.\n\t// This flag ensures backward compatibility by allowing scaffolding in the legacy/deprecated path.\n\tIsLegacyPath bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *WebhookTest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\t// Deprecated: Remove me when remove go/v4\n\t\tconst baseDir = \"api\"\n\t\tpathAPI := baseDir\n\t\tif !f.IsLegacyPath {\n\t\t\tpathAPI = filepath.Join(\"internal\", \"webhook\")\n\t\t}\n\n\t\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\t\tf.Path = filepath.Join(pathAPI, \"%[group]\", \"%[version]\", \"%[kind]_webhook_test.go\")\n\t\t} else {\n\t\t\tf.Path = filepath.Join(pathAPI, \"%[version]\", \"%[kind]_webhook_test.go\")\n\t\t}\n\t}\n\tf.Path = f.Resource.Replacer().Replace(f.Path)\n\tlog.Info(f.Path)\n\n\twebhookTestTemplate := webhookTestTemplate\n\ttemplates := make([]string, 0)\n\tif f.Resource.HasDefaultingWebhook() {\n\t\ttemplates = append(templates, defaultWebhookTestTemplate)\n\t}\n\tif f.Resource.HasValidationWebhook() {\n\t\ttemplates = append(templates, validateWebhookTestTemplate)\n\t}\n\tif f.Resource.HasConversionWebhook() {\n\t\ttemplates = append(templates, conversionWebhookTestTemplate)\n\t}\n\tf.TemplateBody = fmt.Sprintf(webhookTestTemplate, strings.Join(templates, \"\\n\"))\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t}\n\tf.IfNotExistsAction = machinery.IgnoreFile\n\n\treturn nil\n}\n\nconst webhookTestTemplate = `{{ .Boilerplate }}\n\npackage {{ .Resource.Version }}\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t{{ if not .IsLegacyPath -}}\n\t{{ if not (isEmptyStr .Resource.Path) -}}\n\t{{ .Resource.ImportAlias }} \"{{ .Resource.Path }}\"\n\t{{- end }}\n\t{{- end }}\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"{{ .Resource.Kind }} Webhook\", func() {\n\tvar (\n\t\t{{- if .IsLegacyPath -}}\n\t\tobj *{{ .Resource.Kind }}\n\t\t{{- else }}\n\t\tobj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}\n\t\toldObj *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}\n\t\t{{- if .Resource.HasValidationWebhook }}\n\t\tvalidator {{ .Resource.Kind }}CustomValidator\n\t\t{{- end }}\n\t\t{{- if .Resource.HasDefaultingWebhook }}\n\t\tdefaulter {{ .Resource.Kind }}CustomDefaulter\n\t\t{{- end }}\n\t\t{{- end }}\n\t)\n\n\tBeforeEach(func() {\n\t\t{{- if .IsLegacyPath -}}\n\t\tobj = &{{ .Resource.Kind }}{}\n\t\t{{- else }}\n\t\tobj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t\toldObj = &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t\t{{- if .Resource.HasValidationWebhook }}\n\t\tvalidator = {{ .Resource.Kind }}CustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\t{{- end }}\n\t\t{{- if .Resource.HasDefaultingWebhook }}\n\t\tdefaulter = {{ .Resource.Kind }}CustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\t{{- end }}\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\t{{- end }}\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\t%s\n})\n`\n\nconst conversionWebhookTestTemplate = `\nContext(\"When creating {{ .Resource.Kind }} under Conversion Webhook\", func() {\n\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t// Example:\n\t// It(\"Should convert the object correctly\", func() {\n\t{{- if .IsLegacyPath -}}\n\t//     convertedObj := &{{ .Resource.Kind }}{}\n\t{{- else }}\n\t//     convertedObj := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}\n\t{{- end }}\n\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t//     Expect(convertedObj).ToNot(BeNil())\n\t// })\n})\n`\n\nconst validateWebhookTestTemplate = `\nContext(\"When creating or updating {{ .Resource.Kind }} under Validating Webhook\", func() {\n\t// TODO (user): Add logic for validating webhooks\n\t// Example:\n\t// It(\"Should deny creation if a required field is missing\", func() {\n\t//     By(\"simulating an invalid creation scenario\")\n\t//     obj.SomeRequiredField = \"\"\n\t{{- if .IsLegacyPath -}}\n\t//     Expect(obj.ValidateCreate(ctx)).Error().To(HaveOccurred())\n\t{{- else }}\n\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t{{- end }}\n\t// })\n\t//\n\t// It(\"Should admit creation if all required fields are present\", func() {\n\t//     By(\"simulating an invalid creation scenario\")\n\t//     obj.SomeRequiredField = \"valid_value\"\n\t{{- if .IsLegacyPath -}}\n\t//     Expect(obj.ValidateCreate(ctx)).To(BeNil())\n\t{{- else }}\n\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t{{- end }}\n\t// })\n\t//\n\t// It(\"Should validate updates correctly\", func() {\n\t//     By(\"simulating a valid update scenario\")\n\t{{- if .IsLegacyPath -}}\n\t//     oldObj := &Captain{SomeRequiredField: \"valid_value\"}\n\t//     obj.SomeRequiredField = \"updated_value\"\n\t//     Expect(obj.ValidateUpdate(ctx, oldObj)).To(BeNil())\n\t{{- else }}\n\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t//     obj.SomeRequiredField = \"updated_value\"\n\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t{{- end }}\n\t// })\n})\n`\n\nconst defaultWebhookTestTemplate = `\nContext(\"When creating {{ .Resource.Kind }} under Defaulting Webhook\", func() {\n\t// TODO (user): Add logic for defaulting webhooks\n\t// Example:\n\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t//     By(\"simulating a scenario where defaults should be applied\")\n\t{{- if .IsLegacyPath -}}\n\t//     obj.SomeFieldWithDefault = \"\"\n\t//     Expect(obj.Default(ctx)).To(Succeed())\n\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t{{- else }}\n\t//     obj.SomeFieldWithDefault = \"\"\n\t//     By(\"calling the Default method to apply defaults\")\n\t//     defaulter.Default(ctx, obj)\n\t//     By(\"checking that the default values are set\")\n\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t{{- end }}\n\t// })\n})\n`\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_test_updater.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhooks\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst (\n\t// testClosingLine is the closing line of a Describe block in test files\n\ttestClosingLine = \"\\n})\\n\"\n)\n\nvar _ machinery.Template = &WebhookTestUpdater{}\n\n// WebhookTestUpdater updates an existing webhook test file to add validator/defaulter variables\ntype WebhookTestUpdater struct {\n\tmachinery.TemplateMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.ResourceMixin\n}\n\n// GetPath implements file.Builder\nfunc (f *WebhookTestUpdater) GetPath() string {\n\tbaseDir := filepath.Join(\"internal\", \"webhook\")\n\n\tvar path string\n\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\tpath = filepath.Join(baseDir, \"%[group]\", \"%[version]\", \"%[kind]_webhook_test.go\")\n\t} else {\n\t\tpath = filepath.Join(baseDir, \"%[version]\", \"%[kind]_webhook_test.go\")\n\t}\n\n\treturn f.Resource.Replacer().Replace(path)\n}\n\n// GetIfExistsAction implements file.Builder\nfunc (*WebhookTestUpdater) GetIfExistsAction() machinery.IfExistsAction {\n\treturn machinery.OverwriteFile\n}\n\n// SetTemplateDefaults implements file.Template\nfunc (f *WebhookTestUpdater) SetTemplateDefaults() error {\n\tfilePath := f.GetPath()\n\n\t// Read the existing file\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tlog.Warn(\"Unable to read webhook test file, skipping update\", \"file\", filePath, \"error\", err)\n\t\t// Return nil to continue - file might not exist yet\n\t\treturn nil\n\t}\n\n\tfileContent := string(content)\n\tmodified := false\n\n\t// Check if we need to add validator variable and tests\n\tvalidatorVar := fmt.Sprintf(\"validator %sCustomValidator\", f.Resource.Kind)\n\tif f.Resource.HasValidationWebhook() && !bytes.Contains(content, []byte(validatorVar)) {\n\t\tfileContent = f.addValidatorVariable(fileContent)\n\t\tfileContent = f.addValidatorInit(fileContent)\n\t\tfileContent = f.addValidationTestContext(fileContent)\n\t\tmodified = true\n\t}\n\n\t// Check if we need to add defaulter variable and tests\n\tdefaulterVar := fmt.Sprintf(\"defaulter %sCustomDefaulter\", f.Resource.Kind)\n\tif f.Resource.HasDefaultingWebhook() && !bytes.Contains(content, []byte(defaulterVar)) {\n\t\tfileContent = f.addDefaulterVariable(fileContent)\n\t\tfileContent = f.addDefaulterInit(fileContent)\n\t\tfileContent = f.addDefaultingTestContext(fileContent)\n\t\tmodified = true\n\t}\n\n\t// Check if we need to add conversion test context\n\tif f.Resource.HasConversionWebhook() && !bytes.Contains(content, []byte(\"Conversion Webhook\")) {\n\t\tfileContent = f.addConversionTestContext(fileContent)\n\t\tmodified = true\n\t}\n\n\tif !modified {\n\t\t// No updates needed, skip writing\n\t\treturn nil\n\t}\n\n\tf.TemplateBody = fileContent\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n// addValidatorVariable adds the validator variable to the var block\nfunc (f *WebhookTestUpdater) addValidatorVariable(content string) string {\n\tvarName := \"validator\"\n\ttypeName := f.Resource.Kind + \"CustomValidator\"\n\treturn f.addVariableToBlock(content, varName, typeName)\n}\n\n// addDefaulterVariable adds the defaulter variable to the var block\nfunc (f *WebhookTestUpdater) addDefaulterVariable(content string) string {\n\tvarName := \"defaulter\"\n\ttypeName := f.Resource.Kind + \"CustomDefaulter\"\n\treturn f.addVariableToBlock(content, varName, typeName)\n}\n\n// addVariableToBlock adds a variable declaration to the var block before the closing paren\nfunc (f *WebhookTestUpdater) addVariableToBlock(content, varName, typeName string) string {\n\tvarBlockPattern := regexp.MustCompile(`(?s)(var\\s*\\(\\s*)([^)]*?)(\\s*\\))`)\n\n\tif match := varBlockPattern.FindStringSubmatch(content); len(match) > 3 {\n\t\topening := match[1]\n\t\tdeclarations := match[2]\n\t\tclosing := match[3]\n\n\t\tindent := f.detectIndentationInBlock(declarations)\n\t\tif indent == \"\" {\n\t\t\tindent = \"\\t\\t\"\n\t\t}\n\n\t\tvarDecl := fmt.Sprintf(\"\\n%s%s %s\", indent, varName, typeName)\n\t\tnewVarBlock := opening + declarations + varDecl + closing\n\n\t\treturn strings.Replace(content, match[0], newVarBlock, 1)\n\t}\n\n\tlog.Warn(\"Could not find var block in test file\",\n\t\t\"kind\", f.Resource.Kind,\n\t\t\"variable\", varName,\n\t\t\"suggestion\", fmt.Sprintf(\"Manually add '%s %s' to the var block\", varName, typeName))\n\treturn content\n}\n\n// detectIndentationInBlock extracts indentation from existing code\nfunc (f *WebhookTestUpdater) detectIndentationInBlock(blockContent string) string {\n\tlines := strings.Split(blockContent, \"\\n\")\n\tfor i := len(lines) - 1; i >= 0; i-- {\n\t\tline := lines[i]\n\t\ttrimmed := strings.TrimSpace(line)\n\t\tif trimmed != \"\" && trimmed != \"var\" && trimmed != \"(\" {\n\t\t\ttrimmedLeft := strings.TrimLeft(line, \" \\t\")\n\t\t\tif len(line) > len(trimmedLeft) {\n\t\t\t\treturn line[:len(line)-len(trimmedLeft)]\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\\t\\t\"\n}\n\n// addValidatorInit adds validator initialization in BeforeEach\nfunc (f *WebhookTestUpdater) addValidatorInit(content string) string {\n\tvarName := \"validator\"\n\ttypeName := f.Resource.Kind + \"CustomValidator\"\n\treturn f.addWebhookInit(content, varName, typeName)\n}\n\n// addDefaulterInit adds defaulter initialization in BeforeEach\nfunc (f *WebhookTestUpdater) addDefaulterInit(content string) string {\n\tvarName := \"defaulter\"\n\ttypeName := f.Resource.Kind + \"CustomDefaulter\"\n\treturn f.addWebhookInit(content, varName, typeName)\n}\n\n// addWebhookInit adds webhook variable initialization at the end of BeforeEach block\nfunc (f *WebhookTestUpdater) addWebhookInit(content, varName, typeName string) string {\n\tcheckPattern := fmt.Sprintf(\"%s = %s\", varName, typeName)\n\tif strings.Contains(content, checkPattern) {\n\t\treturn content\n\t}\n\n\t// Add at the END of BeforeEach block (before closing brace)\n\tbeforeEachPattern := regexp.MustCompile(`(?s)(BeforeEach\\s*\\(\\s*func\\s*\\(\\s*\\)\\s*\\{)(.*?)(\\n\\s*\\}\\s*\\))`)\n\n\tif match := beforeEachPattern.FindStringSubmatch(content); len(match) > 3 {\n\t\topening := match[1]\n\t\tblockContent := match[2]\n\t\tclosing := match[3]\n\n\t\tclosingLines := strings.Split(closing, \"\\n\")\n\t\tindent := \"\\t\\t\"\n\t\tif len(closingLines) > 1 {\n\t\t\tclosingLine := closingLines[1]\n\t\t\tbaseIndent := closingLine[:len(closingLine)-len(strings.TrimLeft(closingLine, \" \\t\"))]\n\t\t\tindent = baseIndent + \"\\t\"\n\t\t}\n\n\t\tinit := fmt.Sprintf(\"\\n%s%s = %s{}\\n%sExpect(%s).NotTo(BeNil(), \\\"Expected %s to be initialized\\\")\",\n\t\t\tindent, varName, typeName, indent, varName, varName)\n\t\treplacement := opening + blockContent + init + closing\n\t\treturn strings.Replace(content, match[0], replacement, 1)\n\t}\n\n\tlog.Warn(\"Could not find BeforeEach block\",\n\t\t\"kind\", f.Resource.Kind,\n\t\t\"variable\", varName,\n\t\t\"suggestion\", fmt.Sprintf(\"Manually add '%s = %s{}' to BeforeEach\", varName, typeName))\n\treturn content\n}\n\n// addValidationTestContext adds the validation test context\nfunc (f *WebhookTestUpdater) addValidationTestContext(content string) string {\n\ttestContext := fmt.Sprintf(`\n\tContext(\"When creating or updating %s under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n`, f.Resource.Kind)\n\n\treturn f.addContextToEnd(content, testContext)\n}\n\n// addDefaultingTestContext adds the defaulting test context\nfunc (f *WebhookTestUpdater) addDefaultingTestContext(content string) string {\n\ttestContext := fmt.Sprintf(`\n\tContext(\"When creating %s under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n`, f.Resource.Kind)\n\n\treturn f.addContextToEnd(content, testContext)\n}\n\n// addConversionTestContext adds the conversion test context\nfunc (f *WebhookTestUpdater) addConversionTestContext(content string) string {\n\ttestContext := fmt.Sprintf(`\n\tContext(\"When creating %s under Conversion Webhook\", func() {\n\t\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t\t// Example:\n\t\t// It(\"Should convert the object correctly\", func() {\n\t\t//     convertedObj := &%s.%s{}\n\t\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t\t//     Expect(convertedObj).ToNot(BeNil())\n\t\t// })\n\t})\n`, f.Resource.Kind, f.Resource.ImportAlias(), f.Resource.Kind)\n\n\treturn f.addContextToEnd(content, testContext)\n}\n\n// addContextToEnd adds a test context before the Describe block's closing })\nfunc (f *WebhookTestUpdater) addContextToEnd(content, testContext string) string {\n\t// Find the Describe block's closing })\n\tdescribePattern := regexp.MustCompile(`(?s)var\\s*_\\s*=\\s*Describe\\([^}]+`)\n\tif match := describePattern.FindStringIndex(content); match != nil {\n\t\tlastClosing := regexp.MustCompile(`\\n\\}\\)\\s*$`)\n\t\tif closingMatch := lastClosing.FindStringIndex(content); closingMatch != nil {\n\t\t\treturn content[:closingMatch[0]] + testContext + content[closingMatch[0]:]\n\t\t}\n\t}\n\n\t// Fallback: find last }) in file (test files typically end with })\n\tif idx := strings.LastIndex(content, testClosingLine); idx != -1 {\n\t\treturn content[:idx] + testContext + testClosingLine\n\t}\n\n\t// Last resort: append at end of file\n\tcontent = strings.TrimRight(content, \"\\n\")\n\treturn content + testContext + \"\\n\"\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks/webhook_updater.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhooks\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst coreGroup = \"core\"\n\nvar _ machinery.Template = &WebhookUpdater{}\n\n// WebhookUpdater updates an existing webhook file to add additional webhook types\ntype WebhookUpdater struct {\n\tmachinery.TemplateMixin\n\tmachinery.RepositoryMixin\n\tmachinery.MultiGroupMixin\n\tmachinery.BoilerplateMixin\n\tmachinery.ResourceMixin\n\n\t// QualifiedGroupWithDash is the Group domain for the Resource replacing '.' with '-'\n\tQualifiedGroupWithDash string\n\n\t// AdmissionReviewVersions defines value for AdmissionReviewVersions marker\n\tAdmissionReviewVersions string\n}\n\n// GetPath implements file.Builder\nfunc (f *WebhookUpdater) GetPath() string {\n\tbaseDir := filepath.Join(\"internal\", \"webhook\")\n\n\tvar path string\n\tif f.MultiGroup && f.Resource.Group != \"\" {\n\t\tpath = filepath.Join(baseDir, \"%[group]\", \"%[version]\", \"%[kind]_webhook.go\")\n\t} else {\n\t\tpath = filepath.Join(baseDir, \"%[version]\", \"%[kind]_webhook.go\")\n\t}\n\n\treturn f.Resource.Replacer().Replace(path)\n}\n\n// GetIfExistsAction implements file.Builder\nfunc (*WebhookUpdater) GetIfExistsAction() machinery.IfExistsAction {\n\treturn machinery.OverwriteFile\n}\n\n// SetTemplateDefaults implements file.Template\nfunc (f *WebhookUpdater) SetTemplateDefaults() error {\n\tfilePath := f.GetPath()\n\n\t// Read the existing file\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tlog.Error(\"failed to read webhook file\", \"file\", filePath, \"error\", err)\n\t\treturn fmt.Errorf(\"failed to read webhook file: %w\", err)\n\t}\n\n\tf.QualifiedGroupWithDash = strings.ReplaceAll(f.Resource.QualifiedGroup(), \".\", \"-\")\n\tf.AdmissionReviewVersions = \"v1\"\n\n\tfileContent := string(content)\n\tvar newCode strings.Builder\n\n\t// Add defaulting webhook if requested and not already present\n\tdefaulterType := fmt.Sprintf(\"%sCustomDefaulter\", f.Resource.Kind)\n\tif f.Resource.HasDefaultingWebhook() {\n\t\ttypeDefPattern := regexp.MustCompile(fmt.Sprintf(`type\\s+%s\\s+struct`, defaulterType))\n\t\tif typeDefPattern.MatchString(string(content)) {\n\t\t\tlog.Info(\"Defaulting webhook already exists, skipping\", \"kind\", f.Resource.Kind)\n\t\t} else {\n\t\t\tdefaultingCode := f.generateDefaultingWebhookCode()\n\t\t\tif defaultingCode != \"\" {\n\t\t\t\tnewCode.WriteString(defaultingCode)\n\t\t\t}\n\n\t\t\tsetupCode := f.generateDefaulterSetupCode()\n\t\t\tif !strings.Contains(fileContent, fmt.Sprintf(\"WithDefaulter(&%s{})\", defaulterType)) {\n\t\t\t\tfileContent = f.injectBeforeComplete(fileContent, setupCode)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add validation webhook if requested and not already present\n\tvalidatorType := fmt.Sprintf(\"%sCustomValidator\", f.Resource.Kind)\n\tif f.Resource.HasValidationWebhook() {\n\t\ttypeDefPattern := regexp.MustCompile(fmt.Sprintf(`type\\s+%s\\s+struct`, validatorType))\n\t\tif typeDefPattern.MatchString(string(content)) {\n\t\t\tlog.Info(\"Validation webhook already exists, skipping\", \"kind\", f.Resource.Kind)\n\t\t} else {\n\t\t\tif !bytes.Contains(content, []byte(\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\")) {\n\t\t\t\tfileContent = f.addAdmissionImport(fileContent)\n\t\t\t}\n\n\t\t\tvalidationCode := f.generateValidationWebhookCode()\n\t\t\tif validationCode != \"\" {\n\t\t\t\tnewCode.WriteString(validationCode)\n\t\t\t}\n\n\t\t\tsetupCode := f.generateValidatorSetupCode()\n\t\t\tif !strings.Contains(fileContent, fmt.Sprintf(\"WithValidator(&%s{})\", validatorType)) {\n\t\t\t\tfileContent = f.injectBeforeComplete(fileContent, setupCode)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Append new webhook code at the end of the file\n\tif newCode.Len() > 0 {\n\t\tfileContent = strings.TrimRight(fileContent, \"\\n\") + \"\\n\" + newCode.String()\n\t}\n\n\tf.TemplateBody = fileContent\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n// injectBeforeComplete injects webhook setup code before the Complete() call\nfunc (f *WebhookUpdater) injectBeforeComplete(content, code string) string {\n\tcompletePattern := regexp.MustCompile(`(?m)^(\\s*)(?:\\.)?\\s*Complete\\(\\s*\\)`)\n\n\tif match := completePattern.FindStringSubmatch(content); len(match) > 1 {\n\t\tcompleteCall := match[0]\n\t\tbaseIndent := match[1]\n\n\t\tbeforeComplete := content[:strings.Index(content, completeCall)]\n\t\tindent := f.detectIndentationBeforeComplete(beforeComplete, baseIndent)\n\t\tadjustedCode := f.adjustCodeIndentation(code, indent)\n\n\t\tinsertPos := strings.Index(content, completeCall)\n\t\treturn content[:insertPos] + adjustedCode + content[insertPos:]\n\t}\n\n\tlog.Warn(\"Could not find Complete() call in setup function\",\n\t\t\"kind\", f.Resource.Kind,\n\t\t\"suggestion\", \"Manually wire webhook in SetupWebhookWithManager\")\n\treturn content\n}\n\n// detectIndentationBeforeComplete extracts indentation from method chain lines\nfunc (f *WebhookUpdater) detectIndentationBeforeComplete(beforeComplete, baseIndent string) string {\n\tlines := strings.Split(beforeComplete, \"\\n\")\n\n\tfor i := len(lines) - 1; i >= 0 && i >= len(lines)-5; i-- {\n\t\tline := lines[i]\n\t\ttrimmed := strings.TrimSpace(line)\n\n\t\tif strings.HasPrefix(trimmed, \".\") && (strings.Contains(trimmed, \"For(\") ||\n\t\t\tstrings.Contains(trimmed, \"With\") || strings.Contains(trimmed, \"Owns(\")) {\n\t\t\tleadingSpace := line[:len(line)-len(strings.TrimLeft(line, \" \\t\"))]\n\t\t\tif leadingSpace != \"\" {\n\t\t\t\treturn leadingSpace\n\t\t\t}\n\t\t}\n\t}\n\n\tif strings.Contains(baseIndent, \"\\t\") {\n\t\treturn baseIndent + \"\\t\\t\"\n\t}\n\treturn baseIndent + \"        \"\n}\n\n// adjustCodeIndentation replaces existing indentation with target indentation\nfunc (f *WebhookUpdater) adjustCodeIndentation(code, targetIndent string) string {\n\tlines := strings.Split(code, \"\\n\")\n\tadjusted := make([]string, len(lines))\n\n\tfor i, line := range lines {\n\t\tif strings.TrimSpace(line) == \"\" {\n\t\t\tadjusted[i] = line\n\t\t\tcontinue\n\t\t}\n\n\t\ttrimmed := strings.TrimLeft(line, \" \\t\")\n\t\tif len(trimmed) < len(line) {\n\t\t\tadjusted[i] = targetIndent + trimmed\n\t\t} else {\n\t\t\tadjusted[i] = line\n\t\t}\n\t}\n\n\treturn strings.Join(adjusted, \"\\n\")\n}\n\n// addAdmissionImport adds the admission package import after the webhook import\nfunc (f *WebhookUpdater) addAdmissionImport(content string) string {\n\tadmissionImport := \"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\tif strings.Contains(content, admissionImport) {\n\t\treturn content\n\t}\n\n\t// Add after webhook import\n\twebhookPattern := regexp.MustCompile(`(?m)^(\\s*)\"sigs\\.k8s\\.io/controller-runtime/pkg/webhook\"`)\n\n\tif match := webhookPattern.FindStringSubmatch(content); len(match) > 1 {\n\t\tindent := match[1]\n\t\twebhookLine := match[0]\n\t\treplacement := webhookLine + \"\\n\" + indent + `\"` + admissionImport + `\"`\n\t\treturn strings.Replace(content, webhookLine, replacement, 1)\n\t}\n\n\t// Fallback: add to end of import block\n\timportBlockPattern := regexp.MustCompile(`(?s)(import\\s*\\([^)]+)(\\s*\\))`)\n\n\tif match := importBlockPattern.FindStringSubmatch(content); len(match) > 2 {\n\t\tlastImportPattern := regexp.MustCompile(`(?m)^(\\s*)\"[^\"]+\"\\s*$`)\n\t\timports := lastImportPattern.FindAllStringSubmatch(match[1], -1)\n\n\t\tindent := \"\\t\"\n\t\tif len(imports) > 0 && len(imports[len(imports)-1]) > 1 {\n\t\t\tindent = imports[len(imports)-1][1]\n\t\t}\n\n\t\tnewImport := \"\\n\" + indent + `\"` + admissionImport + `\"`\n\t\treplacement := match[1] + newImport + match[2]\n\t\treturn strings.Replace(content, match[0], replacement, 1)\n\t}\n\n\tlog.Warn(\"Could not add admission import\",\n\t\t\"kind\", f.Resource.Kind,\n\t\t\"suggestion\", \"Manually add: \"+admissionImport)\n\treturn content\n}\n\n// generateDefaulterSetupCode generates the setup code for defaulting webhook\nfunc (f *WebhookUpdater) generateDefaulterSetupCode() string {\n\tcode := fmt.Sprintf(\"\\t\\tWithDefaulter(&%sCustomDefaulter{}).\", f.Resource.Kind)\n\tif f.Resource.Webhooks.DefaultingPath != \"\" {\n\t\tcode += fmt.Sprintf(\"\\n\\t\\tWithDefaulterCustomPath(\\\"%s\\\").\", f.Resource.Webhooks.DefaultingPath)\n\t}\n\treturn code + \"\\n\"\n}\n\n// generateValidatorSetupCode generates the setup code for validation webhook\nfunc (f *WebhookUpdater) generateValidatorSetupCode() string {\n\tcode := fmt.Sprintf(\"\\t\\tWithValidator(&%sCustomValidator{}).\", f.Resource.Kind)\n\tif f.Resource.Webhooks.ValidationPath != \"\" {\n\t\tcode += fmt.Sprintf(\"\\n\\t\\tWithValidatorCustomPath(\\\"%s\\\").\", f.Resource.Webhooks.ValidationPath)\n\t}\n\treturn code + \"\\n\"\n}\n\n// generateDefaultingWebhookCode generates the defaulting webhook code\nfunc (f *WebhookUpdater) generateDefaultingWebhookCode() string {\n\tvar code strings.Builder\n\n\t// Webhook marker\n\tdefaultingPath := f.Resource.Webhooks.DefaultingPath\n\tif defaultingPath == \"\" {\n\t\tif f.Resource.Core && f.Resource.QualifiedGroup() == coreGroup {\n\t\t\tdefaultingPath = fmt.Sprintf(\"/mutate--%s-%s\",\n\t\t\t\tf.Resource.Version, strings.ToLower(f.Resource.Kind))\n\t\t} else {\n\t\t\tdefaultingPath = fmt.Sprintf(\"/mutate-%s-%s-%s\",\n\t\t\t\tf.QualifiedGroupWithDash, f.Resource.Version, strings.ToLower(f.Resource.Kind))\n\t\t}\n\t}\n\n\t//nolint:lll\n\tcode.WriteString(fmt.Sprintf(\n\t\t`\n// +kubebuilder:webhook:path=%s,mutating=true,failurePolicy=fail,sideEffects=None,groups=%s,resources=%s,verbs=create;update,versions=%s,name=m%s-%s.kb.io,admissionReviewVersions=%s\n\n// %sCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind %s when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype %sCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n`,\n\t\tdefaultingPath, f.getGroupValue(), f.Resource.Plural, f.Resource.Version,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Version,\n\t\tf.AdmissionReviewVersions,\n\t\tf.Resource.Kind, f.Resource.Kind, f.Resource.Kind))\n\n\t// Default method\n\tobjType := f.Resource.ImportAlias() + \".\" + f.Resource.Kind\n\n\tcode.WriteString(fmt.Sprintf(\n\t\t`// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind %s.\nfunc (d *%sCustomDefaulter) Default(_ context.Context, obj *%s) error {\n\t%slog.Info(\"Defaulting for %s\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n`,\n\t\tf.Resource.Kind, f.Resource.Kind, objType,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Kind))\n\n\treturn code.String()\n}\n\n// generateValidationWebhookCode generates the validation webhook code\nfunc (f *WebhookUpdater) generateValidationWebhookCode() string {\n\tvar code strings.Builder\n\n\t// Webhook marker\n\tvalidationPath := f.Resource.Webhooks.ValidationPath\n\tif validationPath == \"\" {\n\t\tif f.Resource.Core && f.Resource.QualifiedGroup() == coreGroup {\n\t\t\tvalidationPath = fmt.Sprintf(\"/validate--%s-%s\",\n\t\t\t\tf.Resource.Version, strings.ToLower(f.Resource.Kind))\n\t\t} else {\n\t\t\tvalidationPath = fmt.Sprintf(\"/validate-%s-%s-%s\",\n\t\t\t\tf.QualifiedGroupWithDash, f.Resource.Version, strings.ToLower(f.Resource.Kind))\n\t\t}\n\t}\n\n\tcode.WriteString(\n\t\t`// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n`)\n\t//nolint:lll\n\tcode.WriteString(fmt.Sprintf(\n\t\t`// +kubebuilder:webhook:path=%s,mutating=false,failurePolicy=fail,sideEffects=None,groups=%s,resources=%s,verbs=create;update,versions=%s,name=v%s-%s.kb.io,admissionReviewVersions=%s\n\n// %sCustomValidator struct is responsible for validating the %s resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype %sCustomValidator struct{\n\t// TODO(user): Add more fields as needed for validation\n}\n\n`,\n\t\tvalidationPath, f.getGroupValue(), f.Resource.Plural, f.Resource.Version,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Version, f.AdmissionReviewVersions,\n\t\tf.Resource.Kind, f.Resource.Kind, f.Resource.Kind))\n\n\t// Validation methods\n\tobjType := f.Resource.ImportAlias() + \".\" + f.Resource.Kind\n\n\tcode.WriteString(fmt.Sprintf(\n\t\t`// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type %s.\nfunc (v *%sCustomValidator) ValidateCreate(_ context.Context, obj *%s) (admission.Warnings, error) {\n\t%slog.Info(\"Validation for %s upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type %s.\nfunc (v *%sCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *%s) (admission.Warnings, error) {\n\t%slog.Info(\"Validation for %s upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type %s.\nfunc (v *%sCustomValidator) ValidateDelete(_ context.Context, obj *%s) (admission.Warnings, error) {\n\t%slog.Info(\"Validation for %s upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n`,\n\t\tf.Resource.Kind, f.Resource.Kind, objType,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Kind,\n\t\tf.Resource.Kind, f.Resource.Kind, objType,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Kind,\n\t\tf.Resource.Kind, f.Resource.Kind, objType,\n\t\tstrings.ToLower(f.Resource.Kind), f.Resource.Kind))\n\n\treturn code.String()\n}\n\n// getGroupValue returns the group value for webhook markers\nfunc (f *WebhookUpdater) getGroupValue() string {\n\tif f.Resource.Core && f.Resource.QualifiedGroup() == coreGroup {\n\t\treturn `\"\"`\n\t}\n\treturn f.Resource.QualifiedGroup()\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/suite_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestScaffolds(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Scaffolds Integration Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/webhook.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/api\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/cmd\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/hack\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/test/e2e\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds/internal/templates/webhooks\"\n)\n\nvar _ plugins.Scaffolder = &webhookScaffolder{}\n\ntype webhookScaffolder struct {\n\tconfig   config.Config\n\tresource resource.Resource\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n\n\t// force indicates whether to scaffold controller files even if it exists or not\n\tforce bool\n\n\t// Deprecated - TODO: remove it for go/v5\n\t// isLegacy indicates that the resource should be created in the legacy path under the api\n\tisLegacy bool\n}\n\n// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations\nfunc NewWebhookScaffolder(cfg config.Config, res resource.Resource, force bool, isLegacy bool) plugins.Scaffolder {\n\treturn &webhookScaffolder{\n\t\tconfig:   cfg,\n\t\tresource: res,\n\t\tforce:    force,\n\t\tisLegacy: isLegacy,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *webhookScaffolder) Scaffold() error {\n\tlog.Info(\"Writing scaffold for you to edit...\")\n\n\t// Load the boilerplate\n\tboilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath)\n\tif err != nil {\n\t\tif errors.Is(err, afero.ErrFileNotFound) {\n\t\t\tlog.Warn(\"unable to find boilerplate file. \"+\n\t\t\t\t\"This file is used to generate the license header in the project..\\n\"+\n\t\t\t\t\"Note that controller-gen will also use this. Ensure that you \"+\n\t\t\t\t\"add the license file or configure your project accordingly\",\n\t\t\t\t\"file_path\", hack.DefaultBoilerplatePath, \"error\", err)\n\t\t\tboilerplate = []byte(\"\")\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"error scaffolding webhook: failed to load boilerplate: %w\", err)\n\t\t}\n\t}\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t\tmachinery.WithBoilerplate(string(boilerplate)),\n\t\tmachinery.WithResource(&s.resource),\n\t)\n\n\t// Keep track of these values before the update\n\tdoDefaulting := s.resource.HasDefaultingWebhook()\n\tdoValidation := s.resource.HasValidationWebhook()\n\tdoConversion := s.resource.HasConversionWebhook()\n\n\tif err = s.config.UpdateResource(s.resource); err != nil {\n\t\treturn fmt.Errorf(\"error updating resource: %w\", err)\n\t}\n\n\t// Check if webhook files exist\n\twebhookFilePath := s.getWebhookFilePath()\n\twebhookFileExists := false\n\tif _, statErr := s.fs.FS.Stat(webhookFilePath); statErr == nil {\n\t\twebhookFileExists = true\n\t}\n\n\twebhookTestFilePath := s.getWebhookTestFilePath()\n\twebhookTestFileExists := false\n\tif _, statErr := s.fs.FS.Stat(webhookTestFilePath); statErr == nil {\n\t\twebhookTestFileExists = true\n\t}\n\n\t// Scaffold or update webhook file (for all webhook types)\n\t// Note: Conversion webhooks also need a webhook.go file with minimal setup (.For(&Type{}).Complete())\n\t// This is how controller-runtime discovers Hub/Convertible interfaces\n\tif doDefaulting || doValidation || doConversion {\n\t\tif err = s.scaffoldWebhookFile(scaffold, webhookFileExists); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Update main.go to wire webhook setup function (for all webhook types)\n\t\tif err = scaffold.Execute(\n\t\t\t&cmd.MainUpdater{WireWebhook: true, IsLegacyPath: s.isLegacy},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error updating main.go: %w\", err)\n\t\t}\n\t}\n\n\t// Scaffold or update webhook test file (for all webhook types)\n\tif err = s.scaffoldWebhookTestFile(scaffold, webhookTestFileExists); err != nil {\n\t\treturn err\n\t}\n\n\t// Update e2e tests\n\t// WireWebhook controls webhook service readiness checks (for defaulting/validation)\n\t// But conversion webhooks still need CA injection tests (handled inside updater)\n\tif err = scaffold.Execute(\n\t\t&e2e.WebhookTestUpdater{WireWebhook: doDefaulting || doValidation},\n\t); err != nil {\n\t\treturn fmt.Errorf(\"error updating e2e tests: %w\", err)\n\t}\n\n\tif doConversion {\n\t\t// Update the types file to add storage version marker\n\t\tif err = scaffold.Execute(&api.TypesUpdater{}); err != nil {\n\t\t\treturn fmt.Errorf(\"error updating types file with storage version marker: %w\", err)\n\t\t}\n\n\t\tif err = scaffold.Execute(&api.Hub{Force: s.force}); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffold resource with hub: %w\", err)\n\t\t}\n\n\t\tfor _, spoke := range s.resource.Webhooks.Spoke {\n\t\t\tlog.Info(\"Scaffolding for spoke version\", \"version\", spoke)\n\t\t\tif err = scaffold.Execute(&api.Spoke{Force: s.force, SpokeVersion: spoke}); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to scaffold spoke %s: %w\", spoke, err)\n\t\t\t}\n\t\t}\n\n\t\tlog.Info(`Webhook server has been set up for you.\nYou need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)\n\t}\n\n\t// Scaffold webhook suite test for all webhook types\n\t// Note: Conversion webhooks also need the suite to register with envtest\n\tif doDefaulting || doValidation || doConversion {\n\t\tif err = scaffold.Execute(&webhooks.WebhookSuite{IsLegacyPath: s.isLegacy}); err != nil {\n\t\t\treturn fmt.Errorf(\"error scaffold webhook suite: %w\", err)\n\t\t}\n\t}\n\n\t// TODO: remove for go/v5\n\tif !s.isLegacy {\n\t\tif hasInternalController, err := pluginutil.HasFileContentWith(\"Dockerfile\", \"internal/controller\"); err != nil {\n\t\t\tlog.Error(\"failed to read Dockerfile to check if webhook(s) will be properly copied\", \"error\", err)\n\t\t} else if hasInternalController {\n\t\t\tlog.Warn(\"Dockerfile is copying internal/controller; to allow copying webhooks, \" +\n\t\t\t\t\"it will be edited, and `internal/controller` will be replaced by `internal/`\")\n\n\t\t\tif err = pluginutil.ReplaceInFile(\"Dockerfile\", \"internal/controller\", \"internal/\"); err != nil {\n\t\t\t\tlog.Error(\"failed to replace \\\"internal/controller\\\" with \\\"internal/\\\" in the Dockerfile\", \"error\", err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// getWebhookFilePath returns the path to the webhook file\nfunc (s *webhookScaffolder) getWebhookFilePath() string {\n\tbaseDir := \"api\"\n\tif !s.isLegacy {\n\t\tbaseDir = \"internal/webhook\"\n\t}\n\n\tvar path string\n\tif s.config.IsMultiGroup() && s.resource.Group != \"\" {\n\t\tpath = fmt.Sprintf(\"%s/%s/%s/%s_webhook.go\",\n\t\t\tbaseDir, s.resource.Group, s.resource.Version, strings.ToLower(s.resource.Kind))\n\t} else {\n\t\tpath = fmt.Sprintf(\"%s/%s/%s_webhook.go\",\n\t\t\tbaseDir, s.resource.Version, strings.ToLower(s.resource.Kind))\n\t}\n\n\treturn path\n}\n\n// getWebhookTestFilePath returns the path to the webhook test file\nfunc (s *webhookScaffolder) getWebhookTestFilePath() string {\n\tbaseDir := \"api\"\n\tif !s.isLegacy {\n\t\tbaseDir = \"internal/webhook\"\n\t}\n\n\tvar path string\n\tif s.config.IsMultiGroup() && s.resource.Group != \"\" {\n\t\tpath = fmt.Sprintf(\"%s/%s/%s/%s_webhook_test.go\",\n\t\t\tbaseDir, s.resource.Group, s.resource.Version, strings.ToLower(s.resource.Kind))\n\t} else {\n\t\tpath = fmt.Sprintf(\"%s/%s/%s_webhook_test.go\",\n\t\t\tbaseDir, s.resource.Version, strings.ToLower(s.resource.Kind))\n\t}\n\n\treturn path\n}\n\n// scaffoldWebhookFile creates or updates the webhook implementation file\nfunc (s *webhookScaffolder) scaffoldWebhookFile(scaffold *machinery.Scaffold, fileExists bool) error {\n\tif !fileExists || s.force {\n\t\tif err := scaffold.Execute(\n\t\t\t&webhooks.Webhook{Force: s.force, IsLegacyPath: s.isLegacy},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error creating webhook: %w\", err)\n\t\t}\n\t} else if fileExists && !s.force && !s.isLegacy {\n\t\tlog.Info(\"Adding new webhook type to existing file\")\n\t\tif err := scaffold.Execute(\n\t\t\t&webhooks.WebhookUpdater{},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error updating webhook: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// scaffoldWebhookTestFile creates or updates the webhook test file\nfunc (s *webhookScaffolder) scaffoldWebhookTestFile(scaffold *machinery.Scaffold, fileExists bool) error {\n\tif !fileExists || s.force {\n\t\tif err := scaffold.Execute(\n\t\t\t&webhooks.WebhookTest{Force: s.force, IsLegacyPath: s.isLegacy},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error creating webhook test: %w\", err)\n\t\t}\n\t} else if fileExists && !s.force && !s.isLegacy {\n\t\tif err := scaffold.Execute(\n\t\t\t&webhooks.WebhookTestUpdater{},\n\t\t); err != nil {\n\t\t\treturn fmt.Errorf(\"error updating webhook test: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/scaffolds/webhook_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nvar _ = Describe(\"Webhook Incremental Scaffolding\", func() {\n\tvar (\n\t\tkbc *utils.TestContext\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\tkbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, \"GO111MODULE=on\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\tBy(\"initializing a project\")\n\t\terr = kbc.Init(\n\t\t\t\"--domain\", \"test.io\",\n\t\t\t\"--repo\", \"test.io/webhooktest\",\n\t\t)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tAfterEach(func() {\n\t\tBy(\"removing working directory\")\n\t\tkbc.Destroy()\n\t})\n\n\tContext(\"When creating webhooks incrementally\", func() {\n\t\tIt(\"should support adding validation to existing defaulting webhook\", func() {\n\t\t\tBy(\"creating an API\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestIncremental\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating defaulting webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestIncremental\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding validation webhook WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestIncremental\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying both webhooks are present in test file\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testincremental_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"defaulter TestIncrementalCustomDefaulter\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"validator TestIncrementalCustomValidator\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestIncremental under Defaulting Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating or updating TestIncremental under Validating Webhook\\\"\"))\n\t\t})\n\n\t\tIt(\"should support adding defaulting to existing validation webhook\", func() {\n\t\t\tBy(\"creating an API\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestReverse\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating validation webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestReverse\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding defaulting webhook WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestReverse\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying both webhooks are present\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testreverse_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"validator TestReverseCustomValidator\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"defaulter TestReverseCustomDefaulter\"))\n\t\t})\n\n\t\tIt(\"should support conversion-only webhooks without defaulting/validation\", func() {\n\t\t\tBy(\"creating API v1\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestConversion\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating API v2\")\n\t\t\terr = kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v2\",\n\t\t\t\t\"--kind\", \"TestConversion\",\n\t\t\t\t\"--resource=false\", \"--controller=false\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating conversion webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestConversion\",\n\t\t\t\t\"--conversion\",\n\t\t\t\t\"--spoke\", \"v2\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying conversion test context is created\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testconversion_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestConversion under Conversion Webhook\\\"\"))\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"defaulter\"))\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"validator\"))\n\n\t\t\tBy(\"verifying webhook file was created with minimal setup\")\n\t\t\twebhookFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testconversion_webhook.go\")\n\t\t\twebhookContent, err := os.ReadFile(webhookFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"SetupTestConversionWebhookWithManager\"))\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"NewWebhookManagedBy(mgr, &testv1.TestConversion{})\"))\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"Complete()\"))\n\t\t\tExpect(string(webhookContent)).NotTo(ContainSubstring(\"CustomDefaulter\"))\n\t\t\tExpect(string(webhookContent)).NotTo(ContainSubstring(\"CustomValidator\"))\n\n\t\t\tBy(\"verifying conversion webhook IS wired in main.go\")\n\t\t\tmainFile := filepath.Join(kbc.Dir, \"cmd/main.go\")\n\t\t\tmainContent, err := os.ReadFile(mainFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(mainContent)).To(ContainSubstring(\"SetupTestConversionWebhookWithManager\"))\n\n\t\t\tBy(\"verifying e2e test has conversion CA injection check\")\n\t\t\te2eFile := filepath.Join(kbc.Dir, \"test/e2e/e2e_test.go\")\n\t\t\te2eContent, err := os.ReadFile(e2eFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(e2eContent)).To(ContainSubstring(\"CA injection for TestConversion conversion webhook\"))\n\n\t\t\tBy(\"verifying webhook suite test was created\")\n\t\t\tsuiteFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/webhook_suite_test.go\")\n\t\t\t_, err = os.Stat(suiteFile)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Webhook suite test file should exist\")\n\t\t})\n\n\t\tIt(\"should support multiversion scenario: conversion then defaulting/validation\", func() {\n\t\t\tBy(\"creating API v1\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMulti\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating API v2\")\n\t\t\terr = kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v2\",\n\t\t\t\t\"--kind\", \"TestMulti\",\n\t\t\t\t\"--resource=false\", \"--controller=false\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating conversion webhook first\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMulti\",\n\t\t\t\t\"--conversion\",\n\t\t\t\t\"--spoke\", \"v2\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding defaulting and validation webhooks WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMulti\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying all three webhook types are present\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testmulti_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"defaulter TestMultiCustomDefaulter\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"validator TestMultiCustomValidator\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestMulti under Conversion Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestMulti under Defaulting Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating or updating TestMulti under Validating Webhook\\\"\"))\n\n\t\t\tBy(\"verifying defaulting/validation webhooks are wired in main.go\")\n\t\t\tmainFile := filepath.Join(kbc.Dir, \"cmd/main.go\")\n\t\t\tmainContent, err := os.ReadFile(mainFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(mainContent)).To(ContainSubstring(\"SetupTestMultiWebhookWithManager\"))\n\t\t})\n\t})\n\n\tContext(\"When user customizes webhook files\", func() {\n\t\tIt(\"should preserve customizations when adding new webhook types\", func() {\n\t\t\tBy(\"creating an API\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestCustom\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating defaulting webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestCustom\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"simulating user customizations to test file\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testcustom_webhook_test.go\")\n\t\t\ttestContent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"customizing: renaming variables obj->myObj, oldObj->oldMyObj\")\n\t\t\tmodified := strings.ReplaceAll(string(testContent), \"obj       *testv1.TestCustom\", \"myObj     *testv1.TestCustom\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"oldObj    *testv1.TestCustom\", \"oldMyObj  *testv1.TestCustom\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"obj = &testv1.TestCustom{}\", \"myObj = &testv1.TestCustom{}\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"oldObj = &testv1.TestCustom{}\", \"oldMyObj = &testv1.TestCustom{}\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"Expect(oldObj)\", \"Expect(oldMyObj)\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"Expect(obj)\", \"Expect(myObj)\")\n\n\t\t\tBy(\"customizing: adding custom setup code to BeforeEach\")\n\t\t\tcustomCode := `\t\t// Custom test setup\n\t\tmyObj.Name = \"my-test-object\"\n\t\tmyObj.Namespace = \"test-ns\"\n\t\toldMyObj.Name = \"old-object\"\n\t})`\n\t\t\tmodified = strings.Replace(modified, \"\t})\\n\\n\tAfterEach(func() {\", customCode+\"\\n\\n\tAfterEach(func() {\", 1)\n\n\t\t\terr = os.WriteFile(testFile, []byte(modified), 0644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"customizing: adding custom implementation to webhook\")\n\t\t\twebhookFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testcustom_webhook.go\")\n\t\t\twebhookContent, err := os.ReadFile(webhookFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tmodifiedWebhook := strings.ReplaceAll(string(webhookContent),\n\t\t\t\t\"// TODO(user): fill in your defaulting logic.\",\n\t\t\t\t`// My custom defaulting logic\n\tif testcustom.Spec.Replicas == nil {\n\t\treplicas := int32(1)\n\t\ttestcustom.Spec.Replicas = &replicas\n\t}`)\n\n\t\t\terr = os.WriteFile(webhookFile, []byte(modifiedWebhook), 0644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding validation webhook WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestCustom\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying validator was added to test file\")\n\t\t\ttestContent, err = os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"validator TestCustomCustomValidator\"))\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"Context(\\\"When creating or updating TestCustom under Validating Webhook\\\"\"))\n\n\t\t\tBy(\"verifying user's renamed variables are preserved\")\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"myObj     *testv1.TestCustom\"))\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"oldMyObj  *testv1.TestCustom\"))\n\n\t\t\tBy(\"verifying user's custom BeforeEach code is preserved\")\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"myObj.Name = \\\"my-test-object\\\"\"))\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"myObj.Namespace = \\\"test-ns\\\"\"))\n\t\t\tExpect(string(testContent)).To(ContainSubstring(\"oldMyObj.Name = \\\"old-object\\\"\"))\n\n\t\t\tBy(\"verifying user's webhook implementation is preserved\")\n\t\t\twebhookContent, err = os.ReadFile(webhookFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"// My custom defaulting logic\"))\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"testcustom.Spec.Replicas = &replicas\"))\n\n\t\t\tBy(\"verifying validator implementation was added\")\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"type TestCustomCustomValidator struct\"))\n\t\t\tExpect(string(webhookContent)).To(ContainSubstring(\"func (v *TestCustomCustomValidator) ValidateCreate\"))\n\t\t})\n\n\t\tIt(\"should work when user removes TODO comments\", func() {\n\t\t\tBy(\"creating an API\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestNoTODO\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating validation webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestNoTODO\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"simulating user removing TODO comments\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testnotodo_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tmodified := strings.ReplaceAll(string(content), \"// TODO (user): Add any setup logic common to all tests\\n\", \"\")\n\t\t\tmodified = strings.ReplaceAll(modified, \"// TODO (user): Add any teardown logic common to all tests\\n\", \"\")\n\n\t\t\terr = os.WriteFile(testFile, []byte(modified), 0644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding defaulting webhook WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestNoTODO\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying defaulter was added despite missing TODO comments\")\n\t\t\tcontent, err = os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"defaulter TestNoTODOCustomDefaulter\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestNoTODO under Defaulting Webhook\\\"\"))\n\t\t})\n\n\t\tIt(\"should support adding defaulting/validation to existing conversion webhook\", func() {\n\t\t\tBy(\"creating API v1\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMultiversion\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating API v2\")\n\t\t\terr = kbc.CreateAPI(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v2\",\n\t\t\t\t\"--kind\", \"TestMultiversion\",\n\t\t\t\t\"--resource=false\", \"--controller=false\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating conversion webhook\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMultiversion\",\n\t\t\t\t\"--conversion\",\n\t\t\t\t\"--spoke\", \"v2\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying only conversion test context exists initially\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/testmultiversion_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestMultiversion under Conversion Webhook\\\"\"))\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"defaulter\"))\n\t\t\tExpect(string(content)).NotTo(ContainSubstring(\"validator\"))\n\n\t\t\tBy(\"adding defaulting and validation webhooks WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"test\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"TestMultiversion\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying all three webhook types are now present\")\n\t\t\tcontent, err = os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"defaulter TestMultiversionCustomDefaulter\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"validator TestMultiversionCustomValidator\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestMultiversion under Conversion Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating TestMultiversion under Defaulting Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating or updating TestMultiversion under Validating Webhook\\\"\"))\n\t\t})\n\n\t\tIt(\"should correctly scaffold conversion webhook and storage version marker\", func() {\n\t\t\tBy(\"creating API v1\")\n\t\t\terr := kbc.CreateAPI(\n\t\t\t\t\"--group\", \"batch\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"CronJob\",\n\t\t\t\t\"--resource\", \"--controller\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating defaulting and validation webhooks for v1\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"batch\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"CronJob\",\n\t\t\t\t\"--defaulting\",\n\t\t\t\t\"--programmatic-validation\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating API v2 without controller\")\n\t\t\terr = kbc.CreateAPI(\n\t\t\t\t\"--group\", \"batch\",\n\t\t\t\t\"--version\", \"v2\",\n\t\t\t\t\"--kind\", \"CronJob\",\n\t\t\t\t\"--resource=false\", \"--controller=false\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"adding conversion webhook to v1 WITHOUT --force\")\n\t\t\terr = kbc.CreateWebhook(\n\t\t\t\t\"--group\", \"batch\",\n\t\t\t\t\"--version\", \"v1\",\n\t\t\t\t\"--kind\", \"CronJob\",\n\t\t\t\t\"--conversion\",\n\t\t\t\t\"--spoke\", \"v2\",\n\t\t\t\t\"--make=false\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying v1 has all three webhook test contexts\")\n\t\t\ttestFile := filepath.Join(kbc.Dir, \"internal/webhook/v1/cronjob_webhook_test.go\")\n\t\t\tcontent, err := os.ReadFile(testFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating CronJob under Defaulting Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating or updating CronJob under Validating Webhook\\\"\"))\n\t\t\tExpect(string(content)).To(ContainSubstring(\"Context(\\\"When creating CronJob under Conversion Webhook\\\"\"))\n\n\t\t\tBy(\"verifying hub and spoke files were created\")\n\t\t\thubFile := filepath.Join(kbc.Dir, \"api/v1/cronjob_conversion.go\")\n\t\t\tspokeFile := filepath.Join(kbc.Dir, \"api/v2/cronjob_conversion.go\")\n\n\t\t\t_, err = os.Stat(hubFile)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Hub file should exist\")\n\n\t\t\t_, err = os.Stat(spokeFile)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Spoke file should exist\")\n\n\t\t\tBy(\"verifying storage version marker was added\")\n\t\t\ttypesFile := filepath.Join(kbc.Dir, \"api/v1/cronjob_types.go\")\n\t\t\ttypesContent, err := os.ReadFile(typesFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(string(typesContent)).To(ContainSubstring(\"// +kubebuilder:storageversion\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/golang/v4/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestV4Plugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Go V4 Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/webhook.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds\"\n)\n\nvar _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{}\n\ntype createWebhookSubcommand struct {\n\tconfig config.Config\n\t// For help text.\n\tcommandName string\n\n\toptions *goPlugin.Options\n\n\tresource *resource.Resource\n\n\t// force indicates that the resource should be created even if it already exists\n\tforce bool\n\n\t// Deprecated - TODO: remove it for go/v5\n\t// isLegacyPath indicates that the resource should be created in the legacy path under the api\n\tisLegacyPath bool\n\n\t// runMake indicates whether to run make or not after scaffolding APIs\n\trunMake bool\n}\n\nfunc (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tp.commandName = cliMeta.CommandName\n\n\tsubcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting,\nvalidating and/or conversion webhooks.\n`\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Create defaulting and validating webhooks for Group: ship, Version: v1beta1\n  # and Kind: Frigate\n  %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation\n\n  # Create conversion webhook for Group: ship, Version: v1beta1\n  # and Kind: Frigate\n  %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1\n\n  # Create defaulting webhook with custom path for Group: ship, Version: v1beta1\n  # and Kind: Frigate\n  %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting \\\n    --defaulting-path=/my-custom-mutate-path\n  \n  # Create validation webhook with custom path for Group: ship, Version: v1beta1\n  # and Kind: Frigate\n  %[1]s create webhook --group ship --version v1beta1 --kind Frigate \\\n    --programmatic-validation --validation-path=/my-custom-validate-path\n  \n  # Create both defaulting and validation webhooks with different custom paths\n  %[1]s create webhook --group ship --version v1beta1 --kind Frigate \\\n    --defaulting --programmatic-validation \\\n    --defaulting-path=/custom-mutate --validation-path=/custom-validate\n`, cliMeta.CommandName)\n}\n\nfunc (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tp.options = &goPlugin.Options{}\n\n\tfs.BoolVar(&p.runMake, \"make\", true, \"if true, run `make generate` after generating files\")\n\n\tfs.StringVar(&p.options.Plural, \"plural\", \"\", \"resource irregular plural form\")\n\n\tfs.BoolVar(&p.options.DoDefaulting, \"defaulting\", false,\n\t\t\"if set, scaffold the defaulting webhook\")\n\tfs.BoolVar(&p.options.DoValidation, \"programmatic-validation\", false,\n\t\t\"if set, scaffold the validating webhook\")\n\tfs.BoolVar(&p.options.DoConversion, \"conversion\", false,\n\t\t\"if set, scaffold the conversion webhook\")\n\n\tfs.StringSliceVar(&p.options.Spoke, \"spoke\",\n\t\tnil,\n\t\t\"Comma-separated list of spoke versions to be added to the conversion webhook (e.g., --spoke v1,v2)\")\n\n\tfs.StringVar(&p.options.DefaultingPath, \"defaulting-path\", \"\",\n\t\t\"Custom path for the defaulting/mutating webhook (only valid with --defaulting)\")\n\n\tfs.StringVar(&p.options.ValidationPath, \"validation-path\", \"\",\n\t\t\"Custom path for the validation webhook (only valid with --programmatic-validation)\")\n\n\t// TODO: remove for go/v5\n\tfs.BoolVar(&p.isLegacyPath, \"legacy\", false,\n\t\t\"[DEPRECATED] Attempts to create resource under the API directory (legacy path). \"+\n\t\t\t\"This option will be removed in future versions.\")\n\n\tfs.StringVar(&p.options.ExternalAPIPath, \"external-api-path\", \"\",\n\t\t\"Specify the Go package import path for the external API. This is used to scaffold webhooks for resources \"+\n\t\t\t\"defined outside this project (e.g., github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1).\")\n\n\tfs.StringVar(&p.options.ExternalAPIDomain, \"external-api-domain\", \"\",\n\t\t\"Specify the domain name for the external API. This domain is used to generate accurate RBAC \"+\n\t\t\t\"markers and permissions for the external resources (e.g., cert-manager.io).\")\n\n\tfs.StringVar(&p.options.ExternalAPIModule, \"external-api-module\", \"\",\n\t\t\"external API module with optional version (e.g., github.com/cert-manager/cert-manager@v1.18.2)\")\n\n\tfs.BoolVar(&p.force, \"force\", false,\n\t\t\"attempt to create resource even if it already exists\")\n}\n\nfunc (p *createWebhookSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error {\n\tp.resource = res\n\n\tif len(p.options.ExternalAPIPath) != 0 && len(p.options.ExternalAPIDomain) != 0 && p.isLegacyPath {\n\t\treturn errors.New(\"you cannot scaffold webhooks for external types using the legacy path\")\n\t}\n\n\tfor _, spoke := range p.options.Spoke {\n\t\tspoke = strings.TrimSpace(spoke)\n\t\tif !isValidVersion(spoke, res, p.config) {\n\t\t\treturn fmt.Errorf(\"invalid spoke version %q\", spoke)\n\t\t}\n\t\tres.Webhooks.Spoke = append(res.Webhooks.Spoke, spoke)\n\t}\n\n\t// Validate path flags are only used with appropriate webhook types\n\tif p.options.DefaultingPath != \"\" && !p.options.DoDefaulting {\n\t\treturn fmt.Errorf(\"--defaulting-path can only be used with --defaulting\")\n\t}\n\tif p.options.ValidationPath != \"\" && !p.options.DoValidation {\n\t\treturn fmt.Errorf(\"--validation-path can only be used with --programmatic-validation\")\n\t}\n\n\t// Validate that --external-api-module requires --external-api-path\n\tif len(p.options.ExternalAPIModule) != 0 && len(p.options.ExternalAPIPath) == 0 {\n\t\treturn errors.New(\"'--external-api-module' requires '--external-api-path' to be specified\")\n\t}\n\n\tp.options.UpdateResource(p.resource, p.config)\n\n\tif err := p.resource.Validate(); err != nil {\n\t\treturn fmt.Errorf(\"error validating resource: %w\", err)\n\t}\n\n\tif !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() {\n\t\treturn fmt.Errorf(\"%s create webhook requires at least one of --defaulting,\"+\n\t\t\t\" --programmatic-validation and --conversion to be true\", p.commandName)\n\t}\n\n\t// check if resource exist to create webhook\n\tresValue, err := p.config.GetResource(p.resource.GVK)\n\tres = &resValue\n\tif err != nil {\n\t\tif !p.resource.External && !p.resource.Core {\n\t\t\treturn fmt.Errorf(\"%s create webhook requires a previously created API \", p.commandName)\n\t\t}\n\t} else if res.Webhooks != nil && !res.Webhooks.IsEmpty() && !p.force {\n\t\t// Check if user is trying to add a webhook type that already exists\n\t\tif p.resource.HasDefaultingWebhook() && res.Webhooks.Defaulting {\n\t\t\treturn fmt.Errorf(\"defaulting webhook already exists for this resource\")\n\t\t}\n\t\tif p.resource.HasValidationWebhook() && res.Webhooks.Validation {\n\t\t\treturn fmt.Errorf(\"validation webhook already exists for this resource\")\n\t\t}\n\t\tif p.resource.HasConversionWebhook() && res.Webhooks.Conversion {\n\t\t\treturn fmt.Errorf(\"conversion webhook already exists for this resource\")\n\t\t}\n\t\t// If we're here, user is adding a new webhook type to existing resource\n\t\t// Merge the webhook configurations\n\t\tif err := p.resource.Webhooks.Update(res.Webhooks); err != nil {\n\t\t\treturn fmt.Errorf(\"error merging webhook configurations: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force, p.isLegacyPath)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scaffold webhook: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *createWebhookSubcommand) PostScaffold() error {\n\t// If external API with module specified, add it using go get\n\tif p.resource.IsExternal() && p.resource.Module != \"\" {\n\t\tlog.Info(\"Adding external API dependency\", \"module\", p.resource.Module)\n\t\t// Use go get to add the dependency cleanly as a direct requirement\n\t\terr := pluginutil.RunCmd(\"Add external API dependency\", \"go\", \"get\", p.resource.Module)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error adding external API dependency: %w\", err)\n\t\t}\n\t}\n\n\terr := pluginutil.RunCmd(\"Update dependencies\", \"go\", \"mod\", \"tidy\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error updating go dependencies: %w\", err)\n\t}\n\n\tif p.runMake {\n\t\terr = pluginutil.RunCmd(\"Running make\", \"make\", \"generate\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error running make generate: %w\", err)\n\t\t}\n\t}\n\n\tfmt.Print(\"Next: implement your new Webhook and generate the manifests with:\\n$ make manifests\\n\")\n\n\treturn nil\n}\n\n// Helper function to validate spoke versions\nfunc isValidVersion(version string, res *resource.Resource, cfg config.Config) bool {\n\t// Fetch all resources in the config\n\tresources, err := cfg.GetResources()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Iterate through resources and validate if the given version exists for the same Group and Kind\n\tfor _, r := range resources {\n\t\tif r.Group == res.Group && r.Kind == res.Kind && r.Version == version {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// If no matching version is found, return false\n\treturn false\n}\n"
  },
  {
    "path": "pkg/plugins/golang/v4/webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v4\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/resource\"\n\tgoPlugin \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang\"\n)\n\nvar _ = Describe(\"createWebhookSubcommand\", func() {\n\tvar (\n\t\tsubCmd *createWebhookSubcommand\n\t\tcfg    config.Config\n\t\tres    *resource.Resource\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &createWebhookSubcommand{}\n\t\tcfg = cfgv3.New()\n\t\t_ = cfg.SetRepository(\"github.com/example/test\")\n\n\t\tsubCmd.options = &goPlugin.Options{}\n\t\tres = &resource.Resource{\n\t\t\tGVK: resource.GVK{\n\t\t\t\tGroup:   \"crew\",\n\t\t\t\tDomain:  \"test.io\",\n\t\t\t\tVersion: \"v1\",\n\t\t\t\tKind:    \"Captain\",\n\t\t\t},\n\t\t\tPlural:   \"captains\",\n\t\t\tWebhooks: &resource.Webhooks{},\n\t\t}\n\t})\n\n\tIt(\"should reject defaulting-path without --defaulting\", func() {\n\t\tsubCmd.options.DefaultingPath = \"/custom-path\"\n\t\tsubCmd.options.DoDefaulting = false\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"--defaulting-path can only be used with --defaulting\"))\n\t})\n\n\tIt(\"should reject validation-path without --programmatic-validation\", func() {\n\t\tsubCmd.options.ValidationPath = \"/custom-path\"\n\t\tsubCmd.options.DoValidation = false\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"--validation-path can only be used with --programmatic-validation\"))\n\t})\n\n\tIt(\"should require external-api-path when using external-api-module\", func() {\n\t\tsubCmd.options.ExternalAPIModule = \"github.com/external/api@v1.0.0\"\n\t\tsubCmd.options.ExternalAPIPath = \"\"\n\t\tsubCmd.options.DoDefaulting = true\n\n\t\terr := subCmd.InjectResource(res)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"requires '--external-api-path'\"))\n\t})\n\n\tContext(\"isValidVersion\", func() {\n\t\tBeforeEach(func() {\n\t\t\tres = &resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"crew\",\n\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Captain\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tfor _, version := range []string{\"v1\", \"v2\", \"v1beta1\"} {\n\t\t\t\tr := resource.Resource{\n\t\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\t\tGroup:   \"crew\",\n\t\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\t\tVersion: version,\n\t\t\t\t\t\tKind:    \"Captain\",\n\t\t\t\t\t},\n\t\t\t\t\tAPI: &resource.API{CRDVersion: \"v1\"},\n\t\t\t\t}\n\t\t\t\tExpect(cfg.AddResource(r)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return true for existing version with same group and kind\", func() {\n\t\t\tExpect(isValidVersion(\"v2\", res, cfg)).To(BeTrue())\n\t\t\tExpect(isValidVersion(\"v1beta1\", res, cfg)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return false for non-existing version\", func() {\n\t\t\tExpect(isValidVersion(\"v3\", res, cfg)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false for different group\", func() {\n\t\t\tdifferentRes := resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"ship\",\n\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Frigate\",\n\t\t\t\t},\n\t\t\t\tAPI: &resource.API{CRDVersion: \"v1\"},\n\t\t\t}\n\t\t\tExpect(cfg.AddResource(differentRes)).To(Succeed())\n\n\t\t\totherRes := &resource.Resource{GVK: differentRes.GVK}\n\t\t\tExpect(isValidVersion(\"v2\", otherRes, cfg)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false for different kind\", func() {\n\t\t\tdifferentRes := resource.Resource{\n\t\t\t\tGVK: resource.GVK{\n\t\t\t\t\tGroup:   \"crew\",\n\t\t\t\t\tDomain:  \"test.io\",\n\t\t\t\t\tVersion: \"v1\",\n\t\t\t\t\tKind:    \"Pirate\",\n\t\t\t\t},\n\t\t\t\tAPI: &resource.API{CRDVersion: \"v1\"},\n\t\t\t}\n\t\t\tExpect(cfg.AddResource(differentRes)).To(Succeed())\n\n\t\t\totherRes := &resource.Resource{GVK: differentRes.GVK}\n\t\t\tExpect(isValidVersion(\"v2\", otherRes, cfg)).To(BeFalse())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/edit.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tconfig      config.Config\n\tuseGHModels bool\n}\n\nfunc (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = metaDataDescription\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Edit a common project with this plugin\n  %[1]s edit --plugins=%[2]s\n\n  # Edit a common project with GitHub Models enabled (requires repo permissions)\n  %[1]s edit --plugins=%[2]s --use-gh-models\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.useGHModels, \"use-gh-models\", false,\n\t\t\"enable GitHub Models AI summary in the scaffolded workflow (requires GitHub Models permissions)\")\n}\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *editSubcommand) PreScaffold(machinery.Filesystem) error {\n\tif len(p.config.GetCliVersion()) == 0 {\n\t\treturn fmt.Errorf(\n\t\t\t\"you must manually upgrade your project to a version that records the CLI version in PROJECT (`cliVersion`) \" +\n\t\t\t\t\"to allow the `alpha update` command to work properly before using this plugin.\\n\" +\n\t\t\t\t\"More info: https://book.kubebuilder.io/migrations\",\n\t\t)\n\t}\n\treturn nil\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tif err := insertPluginMetaToConfig(p.config, PluginConfig{UseGHModels: p.useGHModels}); err != nil {\n\t\treturn fmt.Errorf(\"error inserting project plugin meta to configuration: %w\", err)\n\t}\n\n\tscaffolder := scaffolds.NewInitScaffolder(p.useGHModels)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding edit subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *editSubcommand) PostScaffold() error {\n\t// Inform users about GitHub Models if they didn't enable it\n\tif !p.useGHModels {\n\t\tlog.Info(\"Consider enabling GitHub Models to get an AI summary to help with the update\")\n\t\tlog.Info(\"Use the --use-gh-models flag if your project/organization has permission to use GitHub Models\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/edit_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"editSubcommand\", func() {\n\tvar (\n\t\tsubCmd *editSubcommand\n\t\tcfg    config.Config\n\t\tfs     machinery.Filesystem\n\t)\n\n\tBeforeEach(func() {\n\t\tsubCmd = &editSubcommand{}\n\t\tcfg = cfgv3.New()\n\t\tfs = machinery.Filesystem{FS: afero.NewMemMapFs()}\n\t\tExpect(subCmd.InjectConfig(cfg)).To(Succeed())\n\t})\n\n\tIt(\"should require cliVersion to be set in PROJECT file\", func() {\n\t\terr := subCmd.PreScaffold(fs)\n\n\t\tExpect(err).To(HaveOccurred())\n\t\tExpect(err.Error()).To(ContainSubstring(\"must manually upgrade your project\"))\n\t\tExpect(err.Error()).To(ContainSubstring(\"cliVersion\"))\n\t})\n\n\tIt(\"should succeed when cliVersion is set\", func() {\n\t\tExpect(cfg.SetCliVersion(\"v4.0.0\")).To(Succeed())\n\n\t\terr := subCmd.PreScaffold(fs)\n\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/init.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds\"\n)\n\nvar _ plugin.InitSubcommand = &initSubcommand{}\n\ntype initSubcommand struct {\n\tconfig      config.Config\n\tuseGHModels bool\n}\n\nfunc (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = metaDataDescription\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Initialize a common project with this plugin\n  %[1]s init --plugins=%[2]s\n\n  # Initialize with GitHub Models enabled (requires repo permissions)\n  %[1]s init --plugins=%[2]s --use-gh-models\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.useGHModels, \"use-gh-models\", false,\n\t\t\"enable GitHub Models AI summary in the scaffolded workflow (requires GitHub Models permissions)\")\n}\n\nfunc (p *initSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tif err := insertPluginMetaToConfig(p.config, PluginConfig{UseGHModels: p.useGHModels}); err != nil {\n\t\treturn fmt.Errorf(\"error inserting project plugin meta to configuration: %w\", err)\n\t}\n\n\tscaffolder := scaffolds.NewInitScaffolder(p.useGHModels)\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding init subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (p *initSubcommand) PostScaffold() error {\n\t// Inform users about GitHub Models if they didn't enable it\n\tif !p.useGHModels {\n\t\tlog.Info(\"Consider enabling GitHub Models to get an AI summary to help with the update\")\n\t\tlog.Info(\"Use the --use-gh-models flag if your project/organization has permission to use GitHub Models\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/plugin.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n)\n\n//nolint:lll\nconst metaDataDescription = `This plugin scaffolds a GitHub Action that helps you keep your project aligned with the latest Kubebuilder improvements. With a tiny amount of setup, you'll receive **automatic issue notifications** whenever a new Kubebuilder release is available. Each issue includes a **compare link** so you can open a Pull Request with one click and review the changes safely.\n\nUnder the hood, the workflow runs 'kubebuilder alpha update' using a **3-way merge strategy** to refresh your scaffold while preserving your code. It creates and pushes an update branch, then opens a GitHub **Issue** containing the PR URL you can use to review and merge.\n\n### How to set it up\n\n1) **Add the plugin**: Use the Kubebuilder CLI to scaffold the automation into your repo.\n2) **Review the workflow**: The file '.github/workflows/auto_update.yml' runs on a schedule to check for updates.\n3) **Permissions required** (via the built-in 'GITHUB_TOKEN'):\n   - **contents: write** — needed to create and push the update branch.\n   - **issues: write** — needed to create the tracking Issue with the PR link.\n   - **models: read** (optional) — only required if using --use-gh-models flag for AI-generated summaries.\n4) **Protect your branches**: Enable **branch protection rules** so automated changes **cannot** be pushed directly. All updates must go through a Pull Request for review.\n\n### Optional: GitHub Models AI Summary\n\nBy default, the workflow does NOT use GitHub Models. To enable AI-generated summaries in GitHub issues:\n  - Ensure your repository/organization has permissions to use GitHub Models.\n  - Re-run: kubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models\n\nWithout this flag, the workflow will still work but won't include AI summaries (avoiding 403 Forbidden errors).`\n\nconst pluginName = \"autoupdate.\" + plugins.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 1, Stage: stage.Alpha}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\teditSubcommand\n\tinitSubcommand\n}\n\nvar _ plugin.Init = Plugin{}\n\n// PluginConfig defines the structure that will be used to track the data\ntype PluginConfig struct {\n\tUseGHModels bool `json:\"useGHModels,omitempty\"`\n}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the Helm plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a autoupdate\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }\n\n// GetInitSubcommand will return the subcommand which is responsible for init autoupdate plugin\nfunc (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Proposes Kubebuilder scaffold updates via GitHub Actions\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n\n// insertPluginMetaToConfig will insert the metadata to the plugin configuration\nfunc insertPluginMetaToConfig(target config.Config, cfg PluginConfig) error {\n\tkey := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})\n\tcanonicalKey := plugin.KeyFor(Plugin{})\n\n\tif err := target.DecodePluginConfig(key, &cfg); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\treturn nil\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {\n\t\t\t\t\tif errors.As(err2, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(err2, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err2)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err)\n\t\t}\n\t}\n\n\tif err := target.EncodePluginConfig(key, cfg); err != nil {\n\t\treturn fmt.Errorf(\"error encoding plugin configuration: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/plugin_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n)\n\nvar _ = Describe(\"Plugin\", func() {\n\tvar p Plugin\n\n\tIt(\"should have correct version and support v3 projects\", func() {\n\t\tExpect(p.Version().Number).To(Equal(1))\n\t\tExpect(p.SupportedProjectVersions()).To(ContainElement(cfgv3.Version))\n\t})\n\n\tIt(\"should not be deprecated\", func() {\n\t\tExpect(p.DeprecationWarning()).To(BeEmpty())\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/scaffolds/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github\"\n)\n\nvar _ plugins.Scaffolder = &initScaffolder{}\n\ntype initScaffolder struct {\n\tconfig config.Config\n\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n\n\t// useGHModels determines if GitHub Models AI summary should be enabled\n\tuseGHModels bool\n}\n\n// NewInitScaffolder returns a new Scaffolder for project initialization operations\nfunc NewInitScaffolder(useGHModels bool) plugins.Scaffolder {\n\treturn &initScaffolder{\n\t\tuseGHModels: useGHModels,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *initScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *initScaffolder) Scaffold() error {\n\tlog.Info(\"Writing scaffold for you to edit...\")\n\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\terr := scaffold.Execute(\n\t\t&github.AutoUpdate{UseGHModels: s.useGHModels},\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to execute init scaffold: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/scaffolds/internal/github/auto_update.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &AutoUpdate{}\n\n// AutoUpdate scaffolds the GitHub Action to lint the project\ntype AutoUpdate struct {\n\tmachinery.TemplateMixin\n\tmachinery.BoilerplateMixin\n\n\t// UseGHModels indicates whether to enable GitHub Models AI summary\n\tUseGHModels bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *AutoUpdate) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"auto_update.yml\")\n\t}\n\n\tf.TemplateBody = autoUpdateTemplate\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst autoUpdateTemplate = `name: Auto Update\n\n# The 'kubebuilder alpha update' command requires write access to the repository to create a branch\n# with the update files and allow you to open a pull request using the link provided in the issue.\n# The branch created will be named in the format kubebuilder-update-from-<from-version>-to-<to-version> by default.\n# To protect your codebase, please ensure that you have branch protection rules configured for your \n# main branches. This will guarantee that no one can bypass a review and push directly to a branch like 'main'.\npermissions:\n  contents: write  # Create and push the update branch\n  issues: write    # Create GitHub Issue with PR link{{ if .UseGHModels }}\n  models: read     # Use GitHub Models for AI summaries{{ end }}\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * 2\" # Every Tuesday at 00:00 UTC\n\njobs:\n  auto-update:\n    runs-on: ubuntu-latest\n    env:\n      GH_TOKEN: {{ \"${{ secrets.GITHUB_TOKEN }}\" }}\n\n    # Checkout the repository.\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n      with:\n        token: {{ \"${{ secrets.GITHUB_TOKEN }}\" }}\n        fetch-depth: 0\n\n    # Configure Git to create commits with the GitHub Actions bot.\n    - name: Configure Git\n      run: |\n        git config --global user.name \"github-actions[bot]\"\n        git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n\n    # Set up Go environment.\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: stable\n\n    # Install Kubebuilder.\n    - name: Install Kubebuilder\n      run: |\n        curl -L -o kubebuilder \"https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)\"\n        chmod +x kubebuilder\n        sudo mv kubebuilder /usr/local/bin/\n        kubebuilder version\n{{ if .UseGHModels }}\n    # Install Models extension for GitHub CLI.\n    - name: Install gh-models extension\n      run: |\n        gh extension install github/gh-models --force\n        gh models --help >/dev/null\n{{ end }}\n    # Run the Kubebuilder alpha update command.\n    # More info: https://kubebuilder.io/reference/commands/alpha_update\n    - name: Run kubebuilder alpha update\n      # Executes the update command with specified flags.\n      # --force: Completes the merge even if conflicts occur, leaving conflict markers.\n      # --push: Automatically pushes the resulting output branch to the 'origin' remote.\n      # --restore-path: Preserves specified paths (e.g., CI workflow files) when squashing.\n      # --open-gh-issue: Creates a GitHub Issue with a link for opening a PR for review.{{ if .UseGHModels }}\n      # --use-gh-models: Adds an AI-generated comment to the created Issue with\n      #   a short overview of the scaffold changes and conflict-resolution guidance (if any).{{ else }}\n      #\n      # WARNING: This workflow does not use GitHub Models AI summary by default.\n      # To enable AI-generated summaries in GitHub issues, you need permissions to use GitHub Models.\n      # If you have the required permissions, re-run:\n      #   kubebuilder edit --plugins=\"autoupdate/v1-alpha\" --use-gh-models{{ end }}\n      run: |\n        kubebuilder alpha update \\\n          --force \\\n          --push \\\n          --restore-path .github/workflows \\\n          --open-gh-issue{{ if .UseGHModels }} \\\n          --use-gh-models{{ end }}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/autoupdate/v1alpha/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestAutoupdateV1Alpha(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Autoupdate V1Alpha Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/commons.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\n// InsertPluginMetaToConfig will insert the metadata to the plugin configuration\nfunc InsertPluginMetaToConfig(target config.Config, cfg pluginConfig) error {\n\tkey := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})\n\tcanonicalKey := plugin.KeyFor(Plugin{})\n\n\tif err := target.DecodePluginConfig(key, &cfg); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\treturn nil\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {\n\t\t\t\t\tif errors.As(err2, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(err2, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err2)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err)\n\t\t}\n\t}\n\n\tif err := target.EncodePluginConfig(key, cfg); err != nil {\n\t\treturn fmt.Errorf(\"error encoding plugin configuration: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/constants.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\n//nolint:lll\nconst metaDataDescription = `This command will add Grafana manifests to the project:\n  - A JSON file includes dashboard manifest that can be directly copied to Grafana Web UI.\n\t('grafana/controller-runtime-metrics.json')\n\nNOTE: This plugin requires:\n- Access to Prometheus\n- Your project must be using controller-runtime to expose the metrics via the controller metrics and they need to be collected by Prometheus.\n- Access to Grafana (https://grafana.com/docs/grafana/latest/setup-grafana/installation/)\nCheck how to enable the metrics for your project by looking at the doc: https://book.kubebuilder.io/reference/metrics.html\n`\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/edit.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage v1alpha\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tconfig config.Config\n}\n\nfunc (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = metaDataDescription\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Edit a common project with this plugin\n  %[1]s edit --plugins=%[2]s\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tif err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {\n\t\treturn fmt.Errorf(\"error inserting project plugin meta to configuration: %w\", err)\n\t}\n\n\tscaffolder := scaffolds.NewEditScaffolder()\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding edit subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n//nolint:dupl\npackage v1alpha\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds\"\n)\n\nvar _ plugin.InitSubcommand = &initSubcommand{}\n\ntype initSubcommand struct {\n\tconfig config.Config\n}\n\nfunc (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = metaDataDescription\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`  # Initialize a common project with this plugin\n  %[1]s init --plugins=%[2]s\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *initSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tif err := InsertPluginMetaToConfig(p.config, pluginConfig{}); err != nil {\n\t\treturn fmt.Errorf(\"error inserting project plugin meta to configuration: %w\", err)\n\t}\n\n\tscaffolder := scaffolds.NewInitScaffolder()\n\tscaffolder.InjectFS(fs)\n\tif err := scaffolder.Scaffold(); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding init subcommand: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/plugin.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n)\n\nconst pluginName = \"grafana.\" + plugins.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 1, Stage: stage.Alpha}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\tinitSubcommand\n\teditSubcommand\n}\n\nvar _ plugin.Init = Plugin{}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the grafana plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetInitSubcommand will return the subcommand which is responsible for initializing and scaffolding grafana manifests\nfunc (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand }\n\n// GetEditSubcommand will return the subcommand which is responsible for adding grafana manifests\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }\n\ntype pluginConfig struct{}\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Generates Grafana Dashboards for metrics\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/edit.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates\"\n)\n\nvar _ plugins.Scaffolder = &editScaffolder{}\n\nconst configFilePath = \"grafana/custom-metrics/config.yaml\"\n\ntype editScaffolder struct {\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewEditScaffolder returns a new Scaffolder for project edition operations\nfunc NewEditScaffolder() plugins.Scaffolder {\n\treturn &editScaffolder{}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *editScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\nfunc fileExist(configFilePath string) bool {\n\tif _, err := os.Stat(configFilePath); os.IsNotExist(err) {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc loadConfig(configPath string) ([]templates.CustomMetricItem, error) {\n\tif !fileExist(configPath) {\n\t\treturn nil, nil\n\t}\n\n\t//nolint:gosec\n\tf, err := os.Open(configPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error loading plugin config: %w\", err)\n\t}\n\n\titems, err := configReader(f)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading config.yaml: %w\", err)\n\t}\n\n\tif err = f.Close(); err != nil {\n\t\treturn nil, fmt.Errorf(\"could not close config.yaml: %w\", err)\n\t}\n\n\treturn items, nil\n}\n\nfunc configReader(reader io.Reader) ([]templates.CustomMetricItem, error) {\n\tyamlFile, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading config.yaml: %w\", err)\n\t}\n\n\tconfig := templates.CustomMetricsConfig{}\n\n\terr = yaml.Unmarshal(yamlFile, &config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing config.yaml: %w\", err)\n\t}\n\n\tvalidatedMetricItems := validateCustomMetricItems(config.CustomMetrics)\n\n\treturn validatedMetricItems, nil\n}\n\nfunc validateCustomMetricItems(rawItems []templates.CustomMetricItem) []templates.CustomMetricItem {\n\t// 1. Filter items of missing `Metric` or `Type`\n\tvar filterResult []templates.CustomMetricItem\n\tfor _, item := range rawItems {\n\t\tif hasFields(item) {\n\t\t\tfilterResult = append(filterResult, item)\n\t\t}\n\t}\n\n\t// 2. Fill Expr and Unit if missing\n\tvalidatedItems := make([]templates.CustomMetricItem, len(filterResult))\n\tfor i, item := range filterResult {\n\t\titem = fillMissingExpr(item)\n\t\tvalidatedItems[i] = fillMissingUnit(item)\n\t}\n\n\treturn validatedItems\n}\n\nfunc hasFields(item templates.CustomMetricItem) bool {\n\t// If `Expr` exists, return true\n\tif item.Expr != \"\" {\n\t\treturn true\n\t}\n\n\t// If `Metric` & valid `Type` exists, return true\n\tmetricType := strings.ToLower(item.Type)\n\tif item.Metric != \"\" && (metricType == \"counter\" || metricType == \"gauge\" || metricType == \"histogram\") {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// TODO: Prom_ql exprs can improved to be more pratical and applicable\nfunc fillMissingExpr(item templates.CustomMetricItem) templates.CustomMetricItem {\n\tif item.Expr == \"\" {\n\t\tswitch strings.ToLower(item.Type) {\n\t\tcase \"counter\":\n\t\t\titem.Expr = \"sum(rate(\" + item.Metric + `{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)`\n\t\tcase \"histogram\":\n\t\t\t//nolint:lll\n\t\t\titem.Expr = \"histogram_quantile(0.90, sum by(instance, le) (rate(\" + item.Metric + `{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])))`\n\t\tdefault: // gauge\n\t\t\titem.Expr = item.Metric\n\t\t}\n\t}\n\treturn item\n}\n\nfunc fillMissingUnit(item templates.CustomMetricItem) templates.CustomMetricItem {\n\tif item.Unit == \"\" {\n\t\tname := strings.ToLower(item.Metric)\n\t\titem.Unit = \"none\"\n\t\tif strings.Contains(name, \"second\") || strings.Contains(name, \"duration\") {\n\t\t\titem.Unit = \"s\"\n\t\t} else if strings.Contains(name, \"byte\") {\n\t\t\titem.Unit = \"bytes\"\n\t\t} else if strings.Contains(name, \"ratio\") {\n\t\t\titem.Unit = \"percent\"\n\t\t}\n\t}\n\treturn item\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *editScaffolder) Scaffold() error {\n\tlog.Info(\"Generating Grafana manifests to visualize controller status...\")\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs)\n\n\tconfigPath := configFilePath\n\n\ttemplatesBuilder := []machinery.Builder{\n\t\t&templates.RuntimeManifest{},\n\t\t&templates.ResourcesManifest{},\n\t\t&templates.CustomMetricsConfigManifest{ConfigPath: configPath},\n\t}\n\n\tconfigItems, err := loadConfig(configPath)\n\tif err == nil && len(configItems) > 0 {\n\t\ttemplatesBuilder = append(templatesBuilder, &templates.CustomMetricsDashManifest{Items: configItems})\n\t} else if err != nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"Error on scaffolding manifest for custom metris:\\n%v\", err)\n\t}\n\n\tif err = scaffold.Execute(templatesBuilder...); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding Grafana manifests: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/edit_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates\"\n)\n\nvar _ = Describe(\"Edit Scaffolder\", func() {\n\tvar (\n\t\tfs         machinery.Filesystem\n\t\tscaffolder *editScaffolder\n\t\ttmpDir     string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"grafana-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Change to tmpDir so relative paths work correctly\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\t\tscaffolder = &editScaffolder{}\n\t\tscaffolder.InjectFS(fs)\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tDescribe(\"configReader\", func() {\n\t\tIt(\"should parse valid custom metrics config\", func() {\n\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: foo_bar\n    type: counter\n  - metric: baz_qux\n    type: histogram\n`\n\t\t\treader := strings.NewReader(configContent)\n\t\t\titems, err := configReader(reader)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(items).To(HaveLen(2))\n\t\t\tExpect(items[0].Metric).To(Equal(\"foo_bar\"))\n\t\t\tExpect(items[0].Type).To(Equal(\"counter\"))\n\t\t\tExpect(items[1].Metric).To(Equal(\"baz_qux\"))\n\t\t\tExpect(items[1].Type).To(Equal(\"histogram\"))\n\t\t})\n\n\t\tIt(\"should handle empty config\", func() {\n\t\t\tconfigContent := `---\ncustomMetrics:\n`\n\t\t\treader := strings.NewReader(configContent)\n\t\t\titems, err := configReader(reader)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(items).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should return error for invalid YAML\", func() {\n\t\t\tconfigContent := `invalid: yaml: content:`\n\t\t\treader := strings.NewReader(configContent)\n\t\t\t_, err := configReader(reader)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\t})\n\n\tDescribe(\"validateCustomMetricItems\", func() {\n\t\tIt(\"should filter out items missing required fields\", func() {\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"valid_metric\", Type: \"counter\"},\n\t\t\t\t{Metric: \"\", Type: \"gauge\"}, // Missing metric\n\t\t\t\t{Metric: \"another_valid\", Type: \"histogram\"},\n\t\t\t\t{Type: \"counter\"}, // Missing metric\n\t\t\t}\n\n\t\t\tvalidated := validateCustomMetricItems(items)\n\t\t\tExpect(validated).To(HaveLen(2))\n\t\t\tExpect(validated[0].Metric).To(Equal(\"valid_metric\"))\n\t\t\tExpect(validated[1].Metric).To(Equal(\"another_valid\"))\n\t\t})\n\n\t\tIt(\"should fill missing expr for counter type\", func() {\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"foo_bar\", Type: \"counter\"},\n\t\t\t}\n\n\t\t\tvalidated := validateCustomMetricItems(items)\n\t\t\tExpect(validated).To(HaveLen(1))\n\t\t\tExpect(validated[0].Expr).To(ContainSubstring(\"sum(rate(foo_bar\"))\n\t\t\tExpect(validated[0].Expr).To(ContainSubstring(`{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}`))\n\t\t})\n\n\t\tIt(\"should fill missing expr for histogram type\", func() {\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"foo_bar\", Type: \"histogram\"},\n\t\t\t}\n\n\t\t\tvalidated := validateCustomMetricItems(items)\n\t\t\tExpect(validated).To(HaveLen(1))\n\t\t\tExpect(validated[0].Expr).To(ContainSubstring(\"histogram_quantile(0.90\"))\n\t\t\tExpect(validated[0].Expr).To(ContainSubstring(\"foo_bar\"))\n\t\t})\n\n\t\tIt(\"should fill missing expr for gauge type\", func() {\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"foo_bar\", Type: \"gauge\"},\n\t\t\t}\n\n\t\t\tvalidated := validateCustomMetricItems(items)\n\t\t\tExpect(validated).To(HaveLen(1))\n\t\t\tExpect(validated[0].Expr).To(Equal(\"foo_bar\"))\n\t\t})\n\n\t\tIt(\"should not override existing expr\", func() {\n\t\t\tcustomExpr := \"my_custom_expr\"\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"foo_bar\", Type: \"counter\", Expr: customExpr},\n\t\t\t}\n\n\t\t\tvalidated := validateCustomMetricItems(items)\n\t\t\tExpect(validated).To(HaveLen(1))\n\t\t\tExpect(validated[0].Expr).To(Equal(customExpr))\n\t\t})\n\t})\n\n\tDescribe(\"hasFields\", func() {\n\t\tIt(\"should return true when expr exists\", func() {\n\t\t\titem := templates.CustomMetricItem{Expr: \"some_expr\"}\n\t\t\tExpect(hasFields(item)).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should return true when metric and valid type exist\", func() {\n\t\t\tvalidTypes := []string{\"counter\", \"gauge\", \"histogram\"}\n\t\t\tfor _, t := range validTypes {\n\t\t\t\titem := templates.CustomMetricItem{Metric: \"foo_bar\", Type: t}\n\t\t\t\tExpect(hasFields(item)).To(BeTrue(), \"Expected type %s to be valid\", t)\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should return false when metric is missing\", func() {\n\t\t\titem := templates.CustomMetricItem{Type: \"counter\"}\n\t\t\tExpect(hasFields(item)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false when type is invalid\", func() {\n\t\t\titem := templates.CustomMetricItem{Metric: \"foo_bar\", Type: \"invalid\"}\n\t\t\tExpect(hasFields(item)).To(BeFalse())\n\t\t})\n\n\t\tIt(\"should return false when both metric and expr are missing\", func() {\n\t\t\titem := templates.CustomMetricItem{Type: \"counter\"}\n\t\t\tExpect(hasFields(item)).To(BeFalse())\n\t\t})\n\t})\n\n\tDescribe(\"fillMissingUnit\", func() {\n\t\tIt(\"should detect seconds unit\", func() {\n\t\t\titems := []templates.CustomMetricItem{\n\t\t\t\t{Metric: \"foo_seconds\"},\n\t\t\t\t{Metric: \"bar_duration\"},\n\t\t\t}\n\n\t\t\tfor _, item := range items {\n\t\t\t\tfilled := fillMissingUnit(item)\n\t\t\t\tExpect(filled.Unit).To(Equal(\"s\"))\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should detect bytes unit\", func() {\n\t\t\titem := templates.CustomMetricItem{Metric: \"foo_bytes\"}\n\t\t\tfilled := fillMissingUnit(item)\n\t\t\tExpect(filled.Unit).To(Equal(\"bytes\"))\n\t\t})\n\n\t\tIt(\"should detect percent unit\", func() {\n\t\t\titem := templates.CustomMetricItem{Metric: \"foo_ratio\"}\n\t\t\tfilled := fillMissingUnit(item)\n\t\t\tExpect(filled.Unit).To(Equal(\"percent\"))\n\t\t})\n\n\t\tIt(\"should default to none for unknown units\", func() {\n\t\t\titem := templates.CustomMetricItem{Metric: \"foo_bar\"}\n\t\t\tfilled := fillMissingUnit(item)\n\t\t\tExpect(filled.Unit).To(Equal(\"none\"))\n\t\t})\n\n\t\tIt(\"should not override existing unit\", func() {\n\t\t\titem := templates.CustomMetricItem{Metric: \"foo_bar\", Unit: \"custom\"}\n\t\t\tfilled := fillMissingUnit(item)\n\t\t\tExpect(filled.Unit).To(Equal(\"custom\"))\n\t\t})\n\t})\n\n\tDescribe(\"Scaffold\", func() {\n\t\tContext(\"when initializing a project with grafana plugin\", func() {\n\t\t\tIt(\"should scaffold the default grafana manifests\", func() {\n\t\t\t\terr := scaffolder.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"creating the controller-runtime metrics dashboard\")\n\t\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\t\tExpect(fileExists(runtimePath)).To(BeTrue())\n\t\t\t\tcontent, err := os.ReadFile(runtimePath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(content)).To(ContainSubstring(\"controller_runtime\"))\n\n\t\t\t\tBy(\"creating the controller resources metrics dashboard\")\n\t\t\t\tresourcesPath := filepath.Join(\"grafana\", \"controller-resources-metrics.json\")\n\t\t\t\tExpect(fileExists(resourcesPath)).To(BeTrue())\n\n\t\t\t\tBy(\"creating the custom metrics config template\")\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\tExpect(fileExists(configPath)).To(BeTrue())\n\t\t\t\tcontent, err = os.ReadFile(configPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(string(content)).To(ContainSubstring(\"customMetrics:\"))\n\t\t\t\tExpect(string(content)).To(ContainSubstring(\"# Example:\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"when editing a project with custom metrics\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\t// First scaffold to create initial structure\n\t\t\t\terr := scaffolder.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should generate custom metrics dashboard for counter and histogram of same metric\", func() {\n\t\t\t\tBy(\"updating the config with counter and histogram for same metric name\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: foo_bar\n    type: counter\n  - metric: foo_bar\n    type: histogram\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running scaffold again to generate the custom metrics dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying the custom metrics dashboard was created\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tExpect(fileExists(dashPath)).To(BeTrue())\n\n\t\t\t\tBy(\"verifying the dashboard contains the counter expression\")\n\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\t\t\t\texpectedCounter := `sum(rate(foo_bar{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)`\n\t\t\t\tExpect(contentStr).To(ContainSubstring(expectedCounter),\n\t\t\t\t\t\"Dashboard should contain counter expression\")\n\n\t\t\t\tBy(\"verifying the dashboard contains the histogram expression\")\n\t\t\t\texpectedHistogram := `histogram_quantile(0.90, sum by(instance, le) ` +\n\t\t\t\t\t`(rate(foo_bar{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])))`\n\t\t\t\tExpect(contentStr).To(ContainSubstring(expectedHistogram),\n\t\t\t\t\t\"Dashboard should contain histogram expression\")\n\t\t\t})\n\n\t\t\tIt(\"should generate dashboard with multiple different metrics\", func() {\n\t\t\t\tBy(\"configuring multiple different metrics\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: http_requests_total\n    type: counter\n  - metric: memory_usage_bytes\n    type: gauge\n  - metric: request_duration_seconds\n    type: histogram\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"generating the dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying all metrics are present in the dashboard\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\n\t\t\t\tExpect(contentStr).To(ContainSubstring(\"http_requests_total\"))\n\t\t\t\tExpect(contentStr).To(ContainSubstring(\"memory_usage_bytes\"))\n\t\t\t\tExpect(contentStr).To(ContainSubstring(\"request_duration_seconds\"))\n\t\t\t\tExpect(contentStr).To(ContainSubstring(\"histogram_quantile\"))\n\t\t\t})\n\n\t\t\tIt(\"should handle metrics with custom expressions\", func() {\n\t\t\t\tBy(\"configuring metrics with custom expressions\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: custom_metric\n    type: counter\n    expr: 'my_custom_expression{label=\"value\"}'\n    unit: custom_unit\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"generating the dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying the custom expression is used\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\n\t\t\t\tExpect(contentStr).To(ContainSubstring(`my_custom_expression{label=\"value\"}`))\n\t\t\t\tExpect(contentStr).To(ContainSubstring(\"custom_unit\"))\n\t\t\t})\n\n\t\t\tIt(\"should auto-detect units from metric names\", func() {\n\t\t\t\tBy(\"configuring metrics with unit-indicating names\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: response_time_seconds\n    type: gauge\n  - metric: memory_bytes\n    type: gauge\n  - metric: error_ratio\n    type: gauge\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"generating the dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying units were auto-detected\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\n\t\t\t\t// The dashboard should contain the auto-detected units\n\t\t\t\tExpect(contentStr).To(ContainSubstring(`\"unit\": \"s\"`))       // seconds\n\t\t\t\tExpect(contentStr).To(ContainSubstring(`\"unit\": \"bytes\"`))   // bytes\n\t\t\t\tExpect(contentStr).To(ContainSubstring(`\"unit\": \"percent\"`)) // percent\n\t\t\t})\n\n\t\t\tIt(\"should skip invalid metrics and only process valid ones\", func() {\n\t\t\t\tBy(\"configuring a mix of valid and invalid metrics\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: valid_counter\n    type: counter\n  - metric: \"\"\n    type: gauge\n  - type: histogram\n  - metric: another_valid\n    type: gauge\n  - metric: invalid_type\n    type: unknown\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"generating the dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying dashboard was created with only valid metrics\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tif fileExists(dashPath) {\n\t\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tcontentStr := string(content)\n\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(\"valid_counter\"))\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(\"another_valid\"))\n\t\t\t\t\tExpect(contentStr).NotTo(ContainSubstring(\"invalid_type\"))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should handle metrics ending with _info suffix specially\", func() {\n\t\t\t\tBy(\"configuring a metric with _info suffix\")\n\t\t\t\tconfigContent := `---\ncustomMetrics:\n  - metric: build_info\n    type: gauge\n`\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr := os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"generating the dashboard\")\n\t\t\t\tscaffolder2 := &editScaffolder{}\n\t\t\t\tscaffolder2.InjectFS(fs)\n\t\t\t\terr = scaffolder2.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying dashboard was created with _info metric using table visualization\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tif fileExists(dashPath) {\n\t\t\t\t\tcontent, err := os.ReadFile(dashPath)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tcontentStr := string(content)\n\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(\"build_info\"))\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(`\"type\": \"table\"`))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tContext(\"when no custom metrics are configured\", func() {\n\t\t\tIt(\"should not create custom metrics dashboard\", func() {\n\t\t\t\tBy(\"scaffolding with default config\")\n\t\t\t\terr := scaffolder.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying custom metrics dashboard was not created\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tExpect(fileExists(dashPath)).To(BeFalse())\n\n\t\t\t\tBy(\"verifying the config template was created\")\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\tExpect(fileExists(configPath)).To(BeTrue())\n\t\t\t})\n\t\t})\n\n\t\tContext(\"when config file has only empty metrics\", func() {\n\t\t\tIt(\"should not create custom metrics dashboard\", func() {\n\t\t\t\tBy(\"creating empty config\")\n\t\t\t\tconfigContent := `---\ncustomMetrics: []\n`\n\t\t\t\terr := os.MkdirAll(filepath.Join(\"grafana\", \"custom-metrics\"), 0o755)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tconfigPath := configFilePath\n\t\t\t\terr = os.WriteFile(configPath, []byte(configContent), 0o644)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"scaffolding\")\n\t\t\t\terr = scaffolder.Scaffold()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying custom metrics dashboard was not created\")\n\t\t\t\tdashPath := filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t\t\t\tExpect(fileExists(dashPath)).To(BeFalse())\n\t\t\t})\n\t\t})\n\t})\n})\n\nfunc fileExists(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/init.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates\"\n)\n\nvar _ plugins.Scaffolder = &initScaffolder{}\n\ntype initScaffolder struct {\n\t// fs is the filesystem that will be used by the scaffolder\n\tfs machinery.Filesystem\n}\n\n// NewInitScaffolder returns a new Scaffolder for project initialization operations\nfunc NewInitScaffolder() plugins.Scaffolder {\n\treturn &initScaffolder{}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *initScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold implements cmdutil.Scaffolder\nfunc (s *initScaffolder) Scaffold() error {\n\tlog.Info(\"Generating Grafana manifests to visualize controller status...\")\n\n\t// Initialize the machinery.Scaffold that will write the files to disk\n\tscaffold := machinery.NewScaffold(s.fs)\n\n\terr := scaffold.Execute(\n\t\t&templates.RuntimeManifest{},\n\t\t&templates.ResourcesManifest{},\n\t\t&templates.CustomMetricsConfigManifest{ConfigPath: configFilePath},\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding Grafana memanifests: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/init_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"Init Scaffolder\", func() {\n\tvar (\n\t\tfs         machinery.Filesystem\n\t\tscaffolder *initScaffolder\n\t\ttmpDir     string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"grafana-init-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Change to tmpDir so relative paths work correctly\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\t\tscaffolder = &initScaffolder{}\n\t\tscaffolder.InjectFS(fs)\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tDescribe(\"Scaffold\", func() {\n\t\tIt(\"should handle re-scaffolding gracefully\", func() {\n\t\t\tBy(\"running scaffolder first time\")\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying files were created\")\n\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\tExpect(fileExistsInit(runtimePath)).To(BeTrue())\n\n\t\t\tBy(\"running scaffolder second time\")\n\t\t\tscaffolder2 := &initScaffolder{}\n\t\t\tscaffolder2.InjectFS(fs)\n\t\t\terr = scaffolder2.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying files still exist\")\n\t\t\tExpect(fileExistsInit(runtimePath)).To(BeTrue())\n\t\t})\n\t})\n})\n\nfunc fileExistsInit(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &CustomMetricsConfigManifest{}\n\n// CustomMetricsConfigManifest scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype CustomMetricsConfigManifest struct {\n\tmachinery.TemplateMixin\n\tConfigPath string\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CustomMetricsConfigManifest) SetTemplateDefaults() error {\n\tf.Path = f.ConfigPath\n\n\tf.TemplateBody = customMetricsConfigTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst customMetricsConfigTemplate = `---\ncustomMetrics:\n#  - metric: # Raw custom metric (required)\n#    type:   # Metric type: counter/gauge/histogram (required)\n#    expr:   # Prom_ql for the metric (optional)\n#    unit:   # Unit of measurement, examples: s,none,bytes,percent,etc. (optional)\n#\n#\n# Example:\n# ---\n# customMetrics:\n#   - metric: foo_bar\n#     unit: none\n#     type: histogram\n#   \texpr: histogram_quantile(0.90, sum by(instance, le) (rate(foo_bar{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])))\n`\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/custom_metrics.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// CustomMetricsConfig represents the configuration for custom metrics\ntype CustomMetricsConfig struct {\n\tCustomMetrics []CustomMetricItem `json:\"customMetrics\"`\n}\n\n// CustomMetricItem defines the config items for the custom metrics\ntype CustomMetricItem struct {\n\tMetric string `json:\"metric\"`\n\tType   string `json:\"type\"`\n\tExpr   string `json:\"expr,omitempty\"`\n\tUnit   string `json:\"unit,omitempty\"`\n}\n\nvar _ machinery.Template = &CustomMetricsDashManifest{}\n\n// CustomMetricsDashManifest scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype CustomMetricsDashManifest struct {\n\tmachinery.TemplateMixin\n\n\tItems []CustomMetricItem\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *CustomMetricsDashManifest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"grafana\", \"custom-metrics\", \"custom-metrics-dashboard.json\")\n\t}\n\n\tdefaultTemplate, err := f.createTemplate()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tf.TemplateBody = defaultTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nvar fns = template.FuncMap{\n\t\"plus1\": func(x int) int {\n\t\treturn x + 1\n\t},\n\t\"hasSuffix\": strings.HasSuffix,\n}\n\nfunc (f *CustomMetricsDashManifest) createTemplate() (string, error) {\n\tt := template.Must(template.New(\"customMetricsDashTemplate\").Funcs(fns).Parse(customMetricsDashTemplate))\n\n\toutputTmpl := &bytes.Buffer{}\n\tif err := t.Execute(outputTmpl, f.Items); err != nil {\n\t\treturn \"\", fmt.Errorf(\"error when generating manifest from config: %w\", err)\n\t}\n\n\treturn outputTmpl.String(), nil\n}\n\nconst customMetricsDashTemplate = `{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [{{ $n := len . }}{{ range $i, $e := . }}\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"{{ .Unit }}\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 24\n      },\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"{{ .Expr }}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"{{ .Metric }} ({{ .Type }})\",\n{{- if hasSuffix .Metric \"_info\" }}\n      \"transformations\": [\n        {\n          \"id\": \"labelsToFields\",\n          \"options\": {\n            \"mode\": \"rows\"\n          }\n        }\n      ],\n      \"type\": \"table\"\n{{- else }}\n      \"type\": \"timeseries\"\n{{- end }}\n    }{{ if ne (plus1 $i) $n }},\n\t\t{{end}}{{end}}\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"observability\",\n          \"value\": \"observability\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Custom-Metrics\",\n  \"weekStart\": \"\"\n}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/resources.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ResourcesManifest{}\n\n// ResourcesManifest scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype ResourcesManifest struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ResourcesManifest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"grafana\", \"controller-resources-metrics.json\")\n\t}\n\n\t// Grafana syntax use {{ }} quite often, which is collided with default delimiter for go template parsing.\n\t// Provide an alternative delimiter here to avoid overlaps.\n\tf.SetDelim(\"[[\", \"]]\")\n\tf.TemplateBody = controllerResourcesTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst controllerResourcesTemplate = `{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(process_cpu_seconds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}[5m]) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller CPU Usage\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"process_resident_memory_bytes{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller Memory Usage\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"observability\",\n          \"value\": \"observability\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Resources-Metrics\",\n  \"weekStart\": \"\"\n}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/internal/templates/runtime.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &RuntimeManifest{}\n\n// RuntimeManifest scaffolds a file that defines the kustomization scheme for the prometheus folder\ntype RuntimeManifest struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *RuntimeManifest) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t}\n\n\t// Grafana syntax use {{ }} quite often, which is collided with default delimiter for go template parsing.\n\t// Provide an alternative delimiter here to avoid overlaps.\n\tf.SetDelim(\"[[\", \"]]\")\n\tf.TemplateBody = controllerRuntimeTemplate\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\n//nolint:lll\nconst controllerRuntimeTemplate = `{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 9,\n      \"panels\": [],\n      \"title\": \"Reconciliation Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 24,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"controller_runtime_active_workers{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{controller}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of workers in use\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliations per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 1\n      },\n      \"id\": 7,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total Reconciliation Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliation errors per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 1\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_errors_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reconciliation Error Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 11,\n      \"panels\": [],\n      \"title\": \"Work Queue Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 22,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"workqueue_depth{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"WorkQueue Depth\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds an item stays in workqueue before being requested\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"normal\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 10\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds For Items Stay In Queue (before being requested) (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 10\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_adds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Add Rate\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How many seconds of work has done that is in progress and hasn't been observed by work_duration.\\nLarge values indicate stuck threads.\\nOne can deduce the number of stuck threads by observing the rate at which this increases.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 23,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(workqueue_unfinished_work_seconds{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unfinished Seconds\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds processing an item from workqueue takes.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 18\n      },\n      \"id\": 19,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds Processing Items From WorkQueue (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of retries handled by workqueue\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 18\n      },\n      \"id\": 17,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_retries_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}} \",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Retries Rate\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": [\n            \"All\"\n          ],\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Runtime-Metrics\",\n  \"weekStart\": \"\"\n}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/scaffolds_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// These tests verify the base scaffolding that both init and edit create.\n// Both scaffolders generate RuntimeManifest, ResourcesManifest, and CustomMetricsConfig.\nvar _ = Describe(\"Base Scaffolds (Init & Edit)\", func() {\n\tvar (\n\t\tfs         machinery.Filesystem\n\t\tscaffolder *initScaffolder\n\t\ttmpDir     string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"grafana-scaffolds-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Change to tmpDir so relative paths work correctly\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\t\tscaffolder = &initScaffolder{}\n\t\tscaffolder.InjectFS(fs)\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tContext(\"when scaffolding grafana manifests\", func() {\n\t\tIt(\"should create controller-runtime metrics dashboard\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying the dashboard file exists\")\n\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\tExpect(fileExistsScaffolds(runtimePath)).To(BeTrue())\n\n\t\t\tBy(\"verifying the dashboard contains controller-runtime metrics\")\n\t\t\tcontent, err := os.ReadFile(runtimePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(\"controller_runtime\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"reconcile\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"workqueue\"))\n\t\t})\n\n\t\tIt(\"should create controller-resources metrics dashboard\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying the dashboard file exists\")\n\t\t\tresourcesPath := filepath.Join(\"grafana\", \"controller-resources-metrics.json\")\n\t\t\tExpect(fileExistsScaffolds(resourcesPath)).To(BeTrue())\n\n\t\t\tBy(\"verifying the dashboard contains CPU and memory metrics\")\n\t\t\tcontent, err := os.ReadFile(resourcesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(\"CPU\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"Memory\"))\n\t\t})\n\n\t\tIt(\"should create custom metrics config template\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying the config file exists\")\n\t\t\tconfigPath := filepath.Join(\"grafana\", \"custom-metrics\", \"config.yaml\")\n\t\t\tExpect(fileExistsScaffolds(configPath)).To(BeTrue())\n\n\t\t\tBy(\"verifying the config has proper structure\")\n\t\t\tcontent, err := os.ReadFile(configPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(\"customMetrics:\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"# Example:\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"metric:\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"type:\"))\n\t\t})\n\n\t\tIt(\"should create valid JSON dashboards\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying runtime dashboard is valid JSON\")\n\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\tcontent, err := os.ReadFile(runtimePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(HavePrefix(\"{\"))\n\t\t\tExpect(contentStr).To(HaveSuffix(\"}\\n\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"__inputs\"`))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"panels\"`))\n\t\t})\n\n\t\tIt(\"should configure Prometheus datasource\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying Prometheus datasource configuration\")\n\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\tcontent, err := os.ReadFile(runtimePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(\"DS_PROMETHEUS\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"type\": \"datasource\"`))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"pluginId\": \"prometheus\"`))\n\t\t})\n\n\t\tIt(\"should include template variables\", func() {\n\t\t\terr := scaffolder.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying template variables exist\")\n\t\t\truntimePath := filepath.Join(\"grafana\", \"controller-runtime-metrics.json\")\n\t\t\tcontent, err := os.ReadFile(runtimePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"templating\"`))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"name\": \"namespace\"`))\n\t\t\tExpect(contentStr).To(ContainSubstring(`\"name\": \"job\"`))\n\t\t})\n\t})\n})\n\nfunc fileExistsScaffolds(path string) bool {\n\t_, err := os.Stat(path)\n\treturn err == nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/grafana/v1alpha/scaffolds/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestGrafanaScaffolders(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Grafana Plugin Scaffolders Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/commons.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nfunc insertPluginMetaToConfig(target config.Config, cfg pluginConfig) error {\n\tkey := plugin.GetPluginKeyForConfig(target.GetPluginChain(), Plugin{})\n\tcanonicalKey := plugin.KeyFor(Plugin{})\n\n\tif err := target.DecodePluginConfig(key, &cfg); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\treturn nil\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err2 := target.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {\n\t\t\t\t\tif errors.As(err2, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(err2, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err2)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err)\n\t\t}\n\t}\n\n\tif err := target.EncodePluginConfig(key, cfg); err != nil {\n\t\treturn fmt.Errorf(\"error encoding plugin config: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/edit.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tconfig config.Config\n\tforce  bool\n}\n\n//nolint:lll\nfunc (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = `Initialize or update a Helm chart to distribute the project under the dist/ directory.\n\n**NOTE** Before running the edit command, ensure you first execute 'make manifests' to regenerate\nthe latest Helm chart with your most recent changes.`\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`# Initialize or update a Helm chart to distribute the project under the dist/ directory\n  %[1]s edit --plugins=%[2]s\n\n# Update the Helm chart under the dist/ directory and overwrite all files\n  %[1]s edit --plugins=%[2]s --force\n\n**IMPORTANT**: If the \"--force\" flag is not used, the following files will not be updated to preserve your customizations:\ndist/chart/\n├── values.yaml\n└── templates/\n    └── manager/\n        └── manager.yaml\n\nThe following files are never updated after their initial creation:\n  - chart/Chart.yaml\n  - chart/templates/_helpers.tpl\n  - chart/.helmignore\n\nAll other files are updated without the usage of the '--force=true' flag\nwhen the edit option is used to ensure that the\nmanifests in the chart align with the latest changes.\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.force, \"force\", false, \"if true, regenerates all the files\")\n}\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\tscaffolder := scaffolds.NewHelmScaffolder(p.config, p.force)\n\tscaffolder.InjectFS(fs)\n\terr := scaffolder.Scaffold()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding Helm chart: %w\", err)\n\t}\n\n\t// Track the resources following a declarative approach\n\treturn insertPluginMetaToConfig(p.config, pluginConfig{})\n}\n\n// PostScaffold automatically uncomments cert-manager installation when webhooks are present\nfunc (p *editSubcommand) PostScaffold() error {\n\thasWebhooks := hasWebhooksWith(p.config)\n\n\tif hasWebhooks {\n\t\tworkflowFile := filepath.Join(\".github\", \"workflows\", \"test-chart.yml\")\n\t\tif _, err := os.Stat(workflowFile); err != nil {\n\t\t\tlog.Info(\n\t\t\t\t\"Workflow file not found, unable to uncomment cert-manager installation\",\n\t\t\t\t\"error\", err,\n\t\t\t\t\"file\", workflowFile,\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\t//nolint:lll\n\t\ttarget := `\n#      - name: Install cert-manager via Helm\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true\n#\n#      - name: Wait for cert-manager to be ready\n#        run: |\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook`\n\t\tif err := util.UncommentCode(workflowFile, target, \"#\"); err != nil {\n\t\t\thasUncommented, errCheck := util.HasFileContentWith(workflowFile, \"- name: Install cert-manager via Helm\")\n\t\t\tif !hasUncommented || errCheck != nil {\n\t\t\t\tlog.Warn(\"Failed to uncomment cert-manager installation in workflow file\", \"error\", err, \"file\", workflowFile)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc hasWebhooksWith(c config.Config) bool {\n\tresources, err := c.GetResources()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor _, res := range resources {\n\t\tif res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/plugin.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n)\n\nconst pluginName = \"helm.\" + plugins.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 1, Stage: stage.Alpha}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\teditSubcommand\n}\n\nvar _ plugin.Edit = Plugin{}\n\ntype pluginConfig struct{}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the Helm plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a helm chart\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Generate Helm Chart (deprecated, use v2-alpha)\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"helm/v1-alpha plugin is deprecated, use helm/v2-alpha instead which \" +\n\t\t\"provides dynamic Helm chart generation from kustomize output\"\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/edit.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\tdeployimagev1alpha1 \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates\"\n\tcharttemplates \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates\"\n\ttemplatescertmanager \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager\"\n\ttemplatesmetrics \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus\"\n\ttemplateswebhooks \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github\"\n)\n\nvar _ plugins.Scaffolder = &editScaffolder{}\n\ntype editScaffolder struct {\n\tconfig config.Config\n\n\tfs machinery.Filesystem\n\n\tforce bool\n}\n\n// NewHelmScaffolder returns a new Scaffolder for HelmPlugin\nfunc NewHelmScaffolder(cfg config.Config, force bool) plugins.Scaffolder {\n\treturn &editScaffolder{\n\t\tconfig: cfg,\n\t\tforce:  force,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *editScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold scaffolds the Helm chart with the necessary files.\nfunc (s *editScaffolder) Scaffold() error {\n\tlog.Info(\"Generating Helm Chart to distribute project\")\n\n\timagesEnvVars := s.getDeployImagesEnvVars()\n\n\tscaffold := machinery.NewScaffold(s.fs,\n\t\tmachinery.WithConfig(s.config),\n\t)\n\n\t// Found webhooks by looking at the config our scaffolds files\n\tmutatingWebhooks, validatingWebhooks, err := s.extractWebhooksFromGeneratedFiles()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to extract webhooks: %w\", err)\n\t}\n\thasWebhooks := hasWebhooksWith(s.config) || (len(mutatingWebhooks) > 0 && len(validatingWebhooks) > 0)\n\n\tbuildScaffold := []machinery.Builder{\n\t\t&github.HelmChartCI{},\n\t\t&templates.HelmChart{},\n\t\t&templates.HelmValues{\n\t\t\tHasWebhooks:  hasWebhooks,\n\t\t\tDeployImages: imagesEnvVars,\n\t\t\tForce:        s.force,\n\t\t},\n\t\t&templates.HelmIgnore{},\n\t\t&charttemplates.HelmHelpers{},\n\t\t&manager.Deployment{\n\t\t\tForce:        s.force,\n\t\t\tDeployImages: len(imagesEnvVars) > 0,\n\t\t\tHasWebhooks:  hasWebhooks,\n\t\t},\n\t\t&templatescertmanager.Certificate{HasWebhooks: hasWebhooks},\n\t\t&templatesmetrics.Service{},\n\t\t&prometheus.Monitor{},\n\t}\n\n\tif len(mutatingWebhooks) > 0 || len(validatingWebhooks) > 0 {\n\t\tbuildScaffold = append(buildScaffold,\n\t\t\t&templateswebhooks.Template{\n\t\t\t\tMutatingWebhooks:   mutatingWebhooks,\n\t\t\t\tValidatingWebhooks: validatingWebhooks,\n\t\t\t},\n\t\t)\n\t}\n\n\tif hasWebhooks {\n\t\tbuildScaffold = append(buildScaffold,\n\t\t\t&templateswebhooks.Service{},\n\t\t)\n\t}\n\n\tif err = scaffold.Execute(buildScaffold...); err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding helm-chart manifests: %w\", err)\n\t}\n\n\t// Copy relevant files from config/ to dist/chart/templates/\n\terr = s.copyConfigFiles()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to copy manifests from config to dist/chart/templates/: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// getDeployImagesEnvVars will return the values to append the envvars for projects\n// which has the APIs scaffolded with DeployImage plugin\nfunc (s *editScaffolder) getDeployImagesEnvVars() map[string]string {\n\tdeployImages := make(map[string]string)\n\n\tpluginConfig := struct {\n\t\tResources []struct {\n\t\t\tKind    string            `json:\"kind\"`\n\t\t\tOptions map[string]string `json:\"options\"`\n\t\t} `json:\"resources\"`\n\t}{}\n\n\tkey := plugin.GetPluginKeyForConfig(s.config.GetPluginChain(), deployimagev1alpha1.Plugin{})\n\tcanonicalKey := plugin.KeyFor(deployimagev1alpha1.Plugin{})\n\n\terr := s.config.DecodePluginConfig(key, &pluginConfig)\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\treturn deployImages\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err2 := s.config.DecodePluginConfig(canonicalKey, &pluginConfig); err2 != nil {\n\t\t\t\t\tif errors.As(err2, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn deployImages\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(err2, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\tlog.Warn(\"error decoding deploy-image configuration\", \"error\", err2, \"key\", canonicalKey)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Warn(\"error decoding deploy-image configuration\", \"error\", err, \"key\", key)\n\t\t}\n\t}\n\n\tfor _, res := range pluginConfig.Resources {\n\t\timage, ok := res.Options[\"image\"]\n\t\tif ok {\n\t\t\tdeployImages[strings.ToUpper(res.Kind)] = image\n\t\t}\n\t}\n\treturn deployImages\n}\n\n// extractWebhooksFromGeneratedFiles parses the files generated by controller-gen under\n// config/webhooks and created Mutating and Validating helper structures to\n// generate the webhook manifest for the helm-chart\nfunc (s *editScaffolder) extractWebhooksFromGeneratedFiles() (mutatingWebhooks []templateswebhooks.DataWebhook,\n\tvalidatingWebhooks []templateswebhooks.DataWebhook, err error,\n) {\n\tmanifestFile := \"config/webhook/manifests.yaml\"\n\n\tif _, err = os.Stat(manifestFile); os.IsNotExist(err) {\n\t\tlog.Info(\"webhook manifests were not found\", \"path\", manifestFile)\n\t\treturn nil, nil, nil\n\t}\n\n\tcontent, err := os.ReadFile(manifestFile)\n\tif err != nil {\n\t\treturn nil, nil,\n\t\t\tfmt.Errorf(\"failed to read %q: %w\", manifestFile, err)\n\t}\n\n\tdocs := strings.SplitSeq(string(content), \"---\")\n\tfor doc := range docs {\n\t\tvar webhookConfig struct {\n\t\t\tKind     string `yaml:\"kind\"`\n\t\t\tWebhooks []struct {\n\t\t\t\tName         string `yaml:\"name\"`\n\t\t\t\tClientConfig struct {\n\t\t\t\t\tService struct {\n\t\t\t\t\t\tName      string `yaml:\"name\"`\n\t\t\t\t\t\tNamespace string `yaml:\"namespace\"`\n\t\t\t\t\t\tPath      string `yaml:\"path\"`\n\t\t\t\t\t} `yaml:\"service\"`\n\t\t\t\t} `yaml:\"clientConfig\"`\n\t\t\t\tRules                   []templateswebhooks.DataWebhookRule `yaml:\"rules\"`\n\t\t\t\tFailurePolicy           string                              `yaml:\"failurePolicy\"`\n\t\t\t\tSideEffects             string                              `yaml:\"sideEffects\"`\n\t\t\t\tAdmissionReviewVersions []string                            `yaml:\"admissionReviewVersions\"`\n\t\t\t} `yaml:\"webhooks\"`\n\t\t}\n\n\t\tif err := yaml.Unmarshal([]byte(doc), &webhookConfig); err != nil {\n\t\t\tlog.Error(\"failed to unmarshal webhook YAML\", \"error\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, w := range webhookConfig.Webhooks {\n\t\t\tfor i := range w.Rules {\n\t\t\t\tif len(w.Rules[i].APIGroups) == 0 {\n\t\t\t\t\tw.Rules[i].APIGroups = []string{\"\"}\n\t\t\t\t}\n\t\t\t}\n\t\t\twebhook := templateswebhooks.DataWebhook{\n\t\t\t\tName:                    w.Name,\n\t\t\t\tServiceName:             fmt.Sprintf(\"%s-webhook-service\", s.config.GetProjectName()),\n\t\t\t\tPath:                    w.ClientConfig.Service.Path,\n\t\t\t\tFailurePolicy:           w.FailurePolicy,\n\t\t\t\tSideEffects:             w.SideEffects,\n\t\t\t\tAdmissionReviewVersions: w.AdmissionReviewVersions,\n\t\t\t\tRules:                   w.Rules,\n\t\t\t}\n\n\t\t\tswitch webhookConfig.Kind {\n\t\t\tcase \"MutatingWebhookConfiguration\":\n\t\t\t\tmutatingWebhooks = append(mutatingWebhooks, webhook)\n\t\t\tcase \"ValidatingWebhookConfiguration\":\n\t\t\t\tvalidatingWebhooks = append(validatingWebhooks, webhook)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn mutatingWebhooks, validatingWebhooks, nil\n}\n\n// Helper function to copy files from config/ to dist/chart/templates/\nfunc (s *editScaffolder) copyConfigFiles() error {\n\tconfigDirs := []struct {\n\t\tSrcDir  string\n\t\tDestDir string\n\t\tSubDir  string\n\t}{\n\t\t{\"config/rbac\", \"dist/chart/templates/rbac\", \"rbac\"},\n\t\t{\"config/crd/bases\", \"dist/chart/templates/crd\", \"crd\"},\n\t\t{\"config/network-policy\", \"dist/chart/templates/network-policy\", \"networkPolicy\"},\n\t}\n\n\tfor _, dir := range configDirs {\n\t\t// Check if the source directory exists\n\t\tif _, err := os.Stat(dir.SrcDir); os.IsNotExist(err) {\n\t\t\t// Skip if the source directory does not exist\n\t\t\tcontinue\n\t\t}\n\n\t\tfiles, err := filepath.Glob(filepath.Join(dir.SrcDir, \"*.yaml\"))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed finding files in %q: %w\", dir.SrcDir, err)\n\t\t}\n\n\t\t// Skip processing if the directory is empty (no matching files)\n\t\tif len(files) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Ensure destination directory exists\n\t\tif err := os.MkdirAll(dir.DestDir, 0o755); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create directory %q: %w\", dir.DestDir, err)\n\t\t}\n\n\t\tfor _, srcFile := range files {\n\t\t\tdestFile := filepath.Join(dir.DestDir, filepath.Base(srcFile))\n\n\t\t\thasConvertionalWebhook := false\n\t\t\tif hasWebhooksWith(s.config) {\n\t\t\t\tresources, err := s.config.GetResources()\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tfor _, res := range resources {\n\t\t\t\t\tif res.HasConversionWebhook() {\n\t\t\t\t\t\thasConvertionalWebhook = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terr := copyFileWithHelmLogic(srcFile, destFile, dir.SubDir, s.config.GetProjectName(), hasConvertionalWebhook)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// copyFileWithHelmLogic reads the source file, modifies the content for Helm, applies patches\n// to spec.conversion if applicable, and writes it to the destination\nfunc copyFileWithHelmLogic(srcFile, destFile, subDir, projectName string, hasConvertionalWebhook bool) error {\n\tif _, err := os.Stat(srcFile); os.IsNotExist(err) {\n\t\tlog.Info(\"Source file does not exist\", \"source_file\", srcFile)\n\t\treturn fmt.Errorf(\"source file does not exist %q: %w\", srcFile, err)\n\t}\n\n\tcontent, err := os.ReadFile(srcFile)\n\tif err != nil {\n\t\tlog.Info(\"Error reading source file\", \"source_file\", srcFile)\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", srcFile, err)\n\t}\n\n\tcontentStr := string(content)\n\n\t// Skip kustomization.yaml or kustomizeconfig.yaml files\n\tif strings.HasSuffix(srcFile, \"kustomization.yaml\") ||\n\t\tstrings.HasSuffix(srcFile, \"kustomizeconfig.yaml\") {\n\t\treturn nil\n\t}\n\n\t// Apply RBAC-specific replacements\n\tif subDir == \"rbac\" {\n\t\tcontentStr = strings.ReplaceAll(contentStr,\n\t\t\t\"name: controller-manager\",\n\t\t\t\"name: {{ .Values.controllerManager.serviceAccountName }}\")\n\t\tcontentStr = strings.Replace(contentStr,\n\t\t\t\"name: metrics-reader\",\n\t\t\tfmt.Sprintf(\"name: %s-metrics-reader\", projectName), 1)\n\n\t\tcontentStr = strings.ReplaceAll(contentStr,\n\t\t\t\"name: metrics-auth-role\",\n\t\t\tfmt.Sprintf(\"name: %s-metrics-auth-role\", projectName))\n\t\tcontentStr = strings.Replace(contentStr,\n\t\t\t\"name: metrics-auth-rolebinding\",\n\t\t\tfmt.Sprintf(\"name: %s-metrics-auth-rolebinding\", projectName), 1)\n\n\t\tif strings.Contains(contentStr, \".Values.controllerManager.serviceAccountName\") &&\n\t\t\tstrings.Contains(contentStr, \"kind: ServiceAccount\") &&\n\t\t\t!strings.Contains(contentStr, \"RoleBinding\") {\n\t\t\t// The generated Service Account does not have the annotations field so we must add it.\n\t\t\tcontentStr = strings.Replace(contentStr,\n\t\t\t\t\"metadata:\", `metadata:\n  {{- if and .Values.controllerManager.serviceAccount .Values.controllerManager.serviceAccount.annotations }}\n  annotations:\n    {{- range $key, $value := .Values.controllerManager.serviceAccount.annotations }}\n    {{ $key }}: {{ $value }}\n    {{- end }}\n  {{- end }}`, 1)\n\t\t}\n\t\tcontentStr = strings.ReplaceAll(contentStr,\n\t\t\t\"name: leader-election-role\",\n\t\t\tfmt.Sprintf(\"name: %s-leader-election-role\", projectName))\n\t\tcontentStr = strings.Replace(contentStr,\n\t\t\t\"name: leader-election-rolebinding\",\n\t\t\tfmt.Sprintf(\"name: %s-leader-election-rolebinding\", projectName), 1)\n\t\tcontentStr = strings.ReplaceAll(contentStr,\n\t\t\t\"name: manager-role\",\n\t\t\tfmt.Sprintf(\"name: %s-manager-role\", projectName))\n\t\tcontentStr = strings.Replace(contentStr,\n\t\t\t\"name: manager-rolebinding\",\n\t\t\tfmt.Sprintf(\"name: %s-manager-rolebinding\", projectName), 1)\n\n\t\t// The generated files do not include the namespace\n\t\tif strings.Contains(contentStr, \"leader-election-rolebinding\") ||\n\t\t\tstrings.Contains(contentStr, \"leader-election-role\") {\n\t\t\tnamespace := `\n  namespace: {{ .Release.Namespace }}`\n\t\t\tcontentStr = strings.Replace(contentStr, \"metadata:\", \"metadata:\"+namespace, 1)\n\t\t}\n\t}\n\n\t// Conditionally handle CRD patches and annotations for CRDs\n\tif subDir == \"crd\" {\n\t\tkind, group := extractKindAndGroupFromFileName(filepath.Base(srcFile))\n\t\thasWebhookPatch := false\n\n\t\t// Retrieve patch content for the CRD's spec.conversion, if it exists\n\t\tpatchContent, patchExists, errPatch := getCRDPatchContent(kind, group)\n\t\tif errPatch != nil {\n\t\t\treturn errPatch\n\t\t}\n\n\t\t// If patch content exists, inject it under spec.conversion with Helm conditional\n\t\tif patchExists {\n\t\t\tconversionSpec := extractConversionSpec(patchContent)\n\t\t\t// Projects scaffolded with old Kubebuilder versions does not have the conversion\n\t\t\t// webhook properly generated because before 4.4.0 this feature was not fully addressed.\n\t\t\t// The patch was added by default when should not. See the related fixes:\n\t\t\t//\n\t\t\t// Issue fixed in release 4.3.1: (which will cause the injection of webhook conditionals for projects without\n\t\t\t// conversion webhooks)\n\t\t\t// (kustomize/v2, go/v4): Corrected the generation of manifests under config/crd/patches\n\t\t\t// to ensure the /convert service patch is only created for webhooks configured with --conversion. (#4280)\n\t\t\t//\n\t\t\t// Conversion webhook fully fixed in release 4.4.0:\n\t\t\t// (kustomize/v2, go/v4): Fixed CA injection for conversion webhooks. Previously, the CA injection\n\t\t\t// was applied incorrectly to all CRDs instead of only conversion types. The issue dates back to release 3.5.0\n\t\t\t// due to kustomize/v2-alpha changes. Now, conversion webhooks are properly generated. (#4254, #4282)\n\t\t\tif len(conversionSpec) > 0 && !hasConvertionalWebhook {\n\t\t\t\tlog.Warn(\"\\n\" +\n\t\t\t\t\t\"============================================================\\n\" +\n\t\t\t\t\t\"| [WARNING] Webhook Patch Issue Detected                   |\\n\" +\n\t\t\t\t\t\"============================================================\\n\" +\n\t\t\t\t\t\"Webhook patch found, but no conversion webhook is configured for this project.\\n\\n\" +\n\t\t\t\t\t\"Note: Older scaffolds have an issue where the conversion webhook patch was \\n\" +\n\t\t\t\t\t\"      scaffolded by default, and conversion webhook injection was not properly limited \\n\" +\n\t\t\t\t\t\"      to specific CRDs.\\n\\n\" +\n\t\t\t\t\t\"Recommended Action:\\n\" +\n\t\t\t\t\t\"   - Upgrade your project to the latest available version.\\n\" +\n\t\t\t\t\t\"   - Consider using the 'alpha generate' command.\\n\\n\" +\n\t\t\t\t\t\"The cert-manager injection and webhook conversion patch found for CRDs will\\n\" +\n\t\t\t\t\t\"be skipped and NOT added to the Helm chart.\\n\" +\n\t\t\t\t\t\"============================================================\")\n\n\t\t\t\thasWebhookPatch = false\n\t\t\t} else {\n\t\t\t\tcontentStr = injectConversionSpecWithCondition(contentStr, conversionSpec)\n\t\t\t\thasWebhookPatch = true\n\t\t\t}\n\t\t}\n\n\t\t// Inject annotations after \"annotations:\" in a single block without extra spaces\n\t\tcontentStr = injectAnnotations(contentStr, hasWebhookPatch)\n\t}\n\n\t// Remove existing labels if necessary\n\tcontentStr = removeLabels(contentStr)\n\n\t// Replace namespace with Helm template variable\n\tcontentStr = strings.ReplaceAll(contentStr, \"namespace: system\", \"namespace: {{ .Release.Namespace }}\")\n\n\tcontentStr = strings.Replace(contentStr, \"metadata:\", `metadata:\n  labels:\n    {{- include \"chart.labels\" . | nindent 4 }}`, 1)\n\n\t// Append project name to webhook service name\n\tcontentStr = strings.ReplaceAll(contentStr, \"name: webhook-service\", \"name: \"+projectName+\"-webhook-service\")\n\n\tvar wrappedContent string\n\tif isMetricRBACFile(subDir, srcFile) {\n\t\twrappedContent = fmt.Sprintf(\n\t\t\t\"{{- if and .Values.rbac.enable .Values.metrics.enable }}\\n%s{{- end -}}\\n\", contentStr)\n\t} else {\n\t\twrappedContent = fmt.Sprintf(\n\t\t\t\"{{- if .Values.%s.enable }}\\n%s{{- end -}}\\n\", subDir, contentStr)\n\t}\n\n\tif err = os.MkdirAll(filepath.Dir(destFile), 0o755); err != nil {\n\t\treturn fmt.Errorf(\"error creating directory %q: %w\", filepath.Dir(destFile), err)\n\t}\n\n\terr = os.WriteFile(destFile, []byte(wrappedContent), 0o644)\n\tif err != nil {\n\t\tlog.Info(\"Error writing destination file\", \"destination_file\", destFile)\n\t\treturn fmt.Errorf(\"error writing destination file %q: %w\", destFile, err)\n\t}\n\n\tlog.Info(\"Successfully copied file\", \"from\", srcFile, \"to\", destFile)\n\treturn nil\n}\n\n// extractKindAndGroupFromFileName extracts the kind and group from a CRD filename\nfunc extractKindAndGroupFromFileName(fileName string) (kind, group string) {\n\tparts := strings.Split(fileName, \"_\")\n\tif len(parts) >= 2 {\n\t\tgroup = strings.Split(parts[0], \".\")[0] // Extract group up to the first dot\n\t\tkind = strings.TrimSuffix(parts[1], \".yaml\")\n\t}\n\treturn kind, group\n}\n\n// getCRDPatchContent finds and reads the appropriate patch content for a given kind and group\nfunc getCRDPatchContent(kind, group string) (string, bool, error) {\n\t// First, look for patches that contain both \"webhook\", the group, and kind in their filename\n\tgroupKindPattern := fmt.Sprintf(\"config/crd/patches/webhook_*%s*%s*.yaml\", group, kind)\n\tpatchFiles, err := filepath.Glob(groupKindPattern)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"failed to list patches: %w\", err)\n\t}\n\n\t// If no group-specific patch found, search for patches that contain only \"webhook\" and the kind\n\tif len(patchFiles) == 0 {\n\t\tkindOnlyPattern := fmt.Sprintf(\"config/crd/patches/webhook_*%s*.yaml\", kind)\n\t\tpatchFiles, err = filepath.Glob(kindOnlyPattern)\n\t\tif err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"failed to list patches: %w\", err)\n\t\t}\n\t}\n\n\t// Read the first matching patch file (if any)\n\tif len(patchFiles) > 0 {\n\t\tpatchContent, err := os.ReadFile(patchFiles[0])\n\t\tif err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"failed to read patch file %q: %w\", patchFiles[0], err)\n\t\t}\n\t\treturn string(patchContent), true, nil\n\t}\n\n\treturn \"\", false, nil\n}\n\n// extractConversionSpec extracts only the conversion section from the patch content\nfunc extractConversionSpec(patchContent string) string {\n\tspecStart := strings.Index(patchContent, \"conversion:\")\n\tif specStart == -1 {\n\t\treturn \"\"\n\t}\n\treturn patchContent[specStart:]\n}\n\n// injectConversionSpecWithCondition inserts the conversion spec under the main spec field with Helm conditional\nfunc injectConversionSpecWithCondition(contentStr, conversionSpec string) string {\n\tspecPosition := strings.Index(contentStr, \"spec:\")\n\tif specPosition == -1 {\n\t\treturn contentStr // No spec field found; return unchanged\n\t}\n\tconditionalSpec := fmt.Sprintf(\"\\n  {{- if .Values.webhook.enable }}\\n  %s\\n  {{- end }}\",\n\t\tstrings.TrimRight(conversionSpec, \"\\n\"))\n\treturn contentStr[:specPosition+5] + conditionalSpec + contentStr[specPosition+5:]\n}\n\n// injectAnnotations inserts the required annotations after the \"annotations:\" field in a single block without\n// extra spaces\nfunc injectAnnotations(contentStr string, hasWebhookPatch bool) string {\n\tannotationsBlock := `\n    {{- if .Values.certmanager.enable }}\n    cert-manager.io/inject-ca-from: \"{{ .Release.Namespace }}/serving-cert\"\n    {{- end }}\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}`\n\tif hasWebhookPatch {\n\t\treturn strings.Replace(contentStr, \"annotations:\", \"annotations:\"+annotationsBlock, 1)\n\t}\n\n\t// Apply only resource policy if no webhook patch\n\tresourcePolicy := `\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}`\n\treturn strings.Replace(contentStr, \"annotations:\", \"annotations:\"+resourcePolicy, 1)\n}\n\n// isMetricRBACFile checks if the file is in the \"rbac\"\n// subdirectory and matches one of the metric-related RBAC filenames\nfunc isMetricRBACFile(subDir, srcFile string) bool {\n\treturn subDir == \"rbac\" && (strings.HasSuffix(srcFile, \"metrics_auth_role.yaml\") ||\n\t\tstrings.HasSuffix(srcFile, \"metrics_auth_role_binding.yaml\") ||\n\t\tstrings.HasSuffix(srcFile, \"metrics_reader_role.yaml\"))\n}\n\n// removeLabels removes any existing labels section from the content\nfunc removeLabels(content string) string {\n\tlabelRegex := `(?m)^  labels:\\n(?:    [^\\n]+\\n)*`\n\tre := regexp.MustCompile(labelRegex)\n\n\treturn re.ReplaceAllString(content, \"\")\n}\n\nfunc hasWebhooksWith(c config.Config) bool {\n\t// Get the list of resources\n\tresources, err := c.GetResources()\n\tif err != nil {\n\t\treturn false // If there's an error getting resources, assume no webhooks\n\t}\n\n\tfor _, res := range resources {\n\t\tif res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/cert-manager/certificate.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhook\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Certificate{}\n\n// Certificate scaffolds the Certificate for webhooks in the Helm chart\ntype Certificate struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// HasWebhooks is true when webhooks were found in the config\n\tHasWebhooks bool\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Certificate) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"certmanager\", \"certificate.yaml\")\n\t}\n\n\tf.TemplateBody = certificateTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst certificateTemplate = `{{` + \"`\" + `{{- if .Values.certmanager.enable }}` + \"`\" + `}}\n# Self-signed Issuer\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\n  name: selfsigned-issuer\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\nspec:\n  selfSigned: {}\n{{- if .HasWebhooks }}\n{{ \"{{- if .Values.webhook.enable }}\" }}\n---\n# Certificate for the webhook\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  annotations:\n    {{ \"{{- if .Values.crd.keep }}\" }}\n    \"helm.sh/resource-policy\": keep\n    {{ \"{{- end }}\" }}\n  name: serving-cert\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\nspec:\n  dnsNames:\n    - {{ .ProjectName }}.{{ \"{{ .Release.Namespace }}\" }}.svc\n    - {{ .ProjectName }}.{{ \"{{ .Release.Namespace }}\" }}.svc.cluster.local\n    - {{ .ProjectName }}-webhook-service.{{ \"{{ .Release.Namespace }}\" }}.svc\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{- end }}\n{{ \"{{- if .Values.metrics.enable }}\" }}\n---\n# Certificate for the metrics\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  annotations:\n    {{ \"{{- if .Values.crd.keep }}\" }}\n    \"helm.sh/resource-policy\": keep\n    {{ \"{{- end }}\" }}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\n  name: metrics-certs\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\nspec:\n  dnsNames:\n    - {{ .ProjectName }}.{{ \"{{ .Release.Namespace }}\" }}.svc\n    - {{ .ProjectName }}.{{ \"{{ .Release.Namespace }}\" }}.svc.cluster.local\n    - {{ .ProjectName }}-metrics-service.{{ \"{{ .Release.Namespace }}\" }}.svc\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmHelpers{}\n\n// HelmHelpers scaffolds the _helpers.tpl file for Helm charts\ntype HelmHelpers struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *HelmHelpers) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"_helpers.tpl\")\n\t}\n\n\tf.TemplateBody = helmHelpersTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst helmHelpersTemplate = `{{` + \"`\" + `{{- define \"chart.name\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- if .Chart }}` + \"`\" + `}}\n  {{` + \"`\" + `{{- if .Chart.Name }}` + \"`\" + `}}\n    {{` + \"`\" + `{{- .Chart.Name | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n  {{` + \"`\" + `{{- else if .Values.nameOverride }}` + \"`\" + `}}\n    {{` + \"`\" + `{{ .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n  {{` + \"`\" + `{{- else }}` + \"`\" + `}}\n    {{ .ProjectName }}\n  {{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- else }}` + \"`\" + `}}\n  {{ .ProjectName }}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{/*\nCommon labels for the chart.\n*/}}\n{{` + \"`\" + `{{- define \"chart.labels\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- if .Chart.AppVersion -}}` + \"`\" + `}}\napp.kubernetes.io/version: {{` + \"`\" + `{{ .Chart.AppVersion | quote }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- if .Chart.Version }}` + \"`\" + `}}\nhelm.sh/chart: {{` + \"`\" + `{{ .Chart.Version | quote }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\napp.kubernetes.io/name: {{` + \"`\" + `{{ include \"chart.name\" . }}` + \"`\" + `}}\napp.kubernetes.io/instance: {{` + \"`\" + `{{ .Release.Name }}` + \"`\" + `}}\napp.kubernetes.io/managed-by: {{` + \"`\" + `{{ .Release.Service }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{/*\nSelector labels for the chart.\n*/}}\n{{` + \"`\" + `{{- define \"chart.selectorLabels\" -}}` + \"`\" + `}}\napp.kubernetes.io/name: {{` + \"`\" + `{{ include \"chart.name\" . }}` + \"`\" + `}}\napp.kubernetes.io/instance: {{` + \"`\" + `{{ .Release.Name }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{/*\nHelper to check if mutating webhooks exist in the services.\n*/}}\n{{` + \"`\" + `{{- define \"chart.hasMutatingWebhooks\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- $hasMutating := false }}` + \"`\" + `}}\n{{` + \"`\" + `{{- range . }}` + \"`\" + `}}\n  {{` + \"`\" + `{{- if eq .type \"mutating\" }}` + \"`\" + `}}\n    {{` + \"`\" + `$hasMutating = true }}{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{ $hasMutating }}}}{{- end }}` + \"`\" + `}}\n\n{{/*\nHelper to check if validating webhooks exist in the services.\n*/}}\n{{` + \"`\" + `{{- define \"chart.hasValidatingWebhooks\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- $hasValidating := false }}` + \"`\" + `}}\n{{` + \"`\" + `{{- range . }}` + \"`\" + `}}\n  {{` + \"`\" + `{{- if eq .type \"validating\" }}` + \"`\" + `}}\n    {{` + \"`\" + `$hasValidating = true }}{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{ $hasValidating }}}}{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/manager/manager.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage manager\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Deployment{}\n\n// Deployment scaffolds the manager Deployment for the Helm chart\ntype Deployment struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// DeployImages if true will scaffold the env with the images\n\tDeployImages bool\n\t// Force if true allow overwrite the scaffolded file\n\tForce bool\n\t// HasWebhooks is true when webhooks were found in the config\n\tHasWebhooks bool\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Deployment) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"manager\", \"manager.yaml\")\n\t}\n\n\tf.TemplateBody = managerDeploymentTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n//nolint:lll\nconst managerDeploymentTemplate = `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .ProjectName }}-controller-manager\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\n    control-plane: controller-manager\nspec:\n  replicas:  {{ \"{{ .Values.controllerManager.replicas }}\" }}\n  selector:\n    matchLabels:\n      {{ \"{{- include \\\"chart.selectorLabels\\\" . | nindent 6 }}\" }}\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        {{ \"{{- include \\\"chart.labels\\\" . | nindent 8 }}\" }}\n        control-plane: controller-manager\n        {{ \"{{- if and .Values.controllerManager.pod .Values.controllerManager.pod.labels }}\" }}\n        {{ \"{{- range $key, $value := .Values.controllerManager.pod.labels }}\" }}\n        {{ \"{{ $key }}\" }}: {{ \"{{ $value }}\" }}\n        {{ \"{{- end }}\" }}\n        {{ \"{{- end }}\" }}\n    spec:\n      containers:\n        - name: manager\n          args:\n            {{ \"{{- range .Values.controllerManager.container.args }}\" }}\n            - {{ \"{{ . }}\" }}\n            {{ \"{{- end }}\" }}\n          command:\n            - /manager\n          image: {{ \"{{ .Values.controllerManager.container.image.repository }}\" }}:{{ \"{{ .Values.controllerManager.container.image.tag }}\" }}\n          {{ \"{{- if .Values.controllerManager.container.imagePullPolicy }}\" }}\n          imagePullPolicy: {{ \"{{ .Values.controllerManager.container.imagePullPolicy }}\" }}\n          {{ \"{{- end }}\" }}\n          {{ \"{{- if .Values.controllerManager.container.env }}\" }}\n          env:\n            {{ \"{{- range $key, $value := .Values.controllerManager.container.env }}\" }}\n            - name: {{ \"{{ $key }}\" }}\n              value: {{ \"{{ $value }}\" }}\n            {{ \"{{- end }}\" }}\n          {{ \"{{- end }}\" }}\n          livenessProbe:\n            {{ \"{{- toYaml .Values.controllerManager.container.livenessProbe | nindent 12 }}\" }}\n          readinessProbe:\n            {{ \"{{- toYaml .Values.controllerManager.container.readinessProbe | nindent 12 }}\" }}\n{{- if .HasWebhooks }}\n          {{ \"{{- if .Values.webhook.enable }}\" }}\n          ports:\n            - containerPort: 9443\n              name: webhook-server\n              protocol: TCP\n          {{ \"{{- end }}\" }}\n{{- end }}\n          resources:\n            {{ \"{{- toYaml .Values.controllerManager.container.resources | nindent 12 }}\" }}\n          securityContext:\n            {{ \"{{- toYaml .Values.controllerManager.container.securityContext | nindent 12 }}\" }}\n{{- if .HasWebhooks }}\n          {{ \"{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}\" }}\n{{- else }}\n          {{ \"{{- if and .Values.certmanager.enable .Values.metrics.enable }}\" }}\n{{- end }}\n          volumeMounts:\n{{- if .HasWebhooks }}\n            {{ \"{{- if and .Values.webhook.enable .Values.certmanager.enable }}\" }}\n            - name: webhook-cert\n              mountPath: /tmp/k8s-webhook-server/serving-certs\n              readOnly: true\n            {{ \"{{- end }}\" }}\n{{- end }}\n            {{ \"{{- if and .Values.metrics.enable .Values.certmanager.enable }}\" }}\n            - name: metrics-certs\n              mountPath: /tmp/k8s-metrics-server/metrics-certs\n              readOnly: true\n            {{ \"{{- end }}\" }}\n          {{ \"{{- end }}\" }}\n      securityContext:\n        {{ \"{{- toYaml .Values.controllerManager.securityContext | nindent 8 }}\" }}\n      serviceAccountName: {{ \"{{ .Values.controllerManager.serviceAccountName }}\" }}\n      terminationGracePeriodSeconds: {{ \"{{ .Values.controllerManager.terminationGracePeriodSeconds }}\" }}\n{{- if .HasWebhooks }}\n      {{ \"{{- if and .Values.certmanager.enable (or .Values.webhook.enable .Values.metrics.enable) }}\" }}\n{{- else }}\n      {{ \"{{- if and .Values.certmanager.enable .Values.metrics.enable }}\" }}\n{{- end }}\n      volumes:\n{{- if .HasWebhooks }}\n        {{ \"{{- if and .Values.webhook.enable .Values.certmanager.enable }}\" }}\n        - name: webhook-cert\n          secret:\n            secretName: webhook-server-cert\n        {{ \"{{- end }}\" }}\n{{- end }}\n        {{ \"{{- if and .Values.metrics.enable .Values.certmanager.enable }}\" }}\n        - name: metrics-certs\n          secret:\n            secretName: metrics-server-cert\n        {{ \"{{- end }}\" }}\n      {{ \"{{- end }}\" }}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/metrics/metrics_service.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage metrics\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Service{}\n\n// Service scaffolds the Service for metrics in the Helm chart\ntype Service struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Service) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"metrics\", \"metrics-service.yaml\")\n\t}\n\n\tf.TemplateBody = metricsServiceTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst metricsServiceTemplate = `{{` + \"`\" + `{{- if .Values.metrics.enable }}` + \"`\" + `}}\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .ProjectName }}-controller-manager-metrics-service\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\n    control-plane: controller-manager\nspec:\n  ports:\n    - port: 8443\n      targetPort: 8443\n      protocol: TCP\n      name: https\n  selector:\n    control-plane: controller-manager\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/prometheus/monitor.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage prometheus\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Monitor{}\n\n// Monitor scaffolds the ServiceMonitor for Prometheus in the Helm chart\ntype Monitor struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Monitor) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"prometheus\", \"monitor.yaml\")\n\t}\n\n\tf.TemplateBody = monitorTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst monitorTemplate = `# To integrate with Prometheus.\n{{ \"{{- if .Values.prometheus.enable }}\" }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\n    control-plane: controller-manager\n  name: {{ .ProjectName }}-controller-manager-metrics-monitor\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        {{ \"{{- if .Values.certmanager.enable }}\" }}\n        serverName: {{ .ProjectName }}-controller-manager-metrics-service.{{ \"{{ .Release.Namespace }}\" }}.svc\n        # Apply secure TLS configuration with cert-manager\n        insecureSkipVerify: false\n        ca:\n          secret:\n            name: metrics-server-cert\n            key: ca.crt\n        cert:\n          secret:\n            name: metrics-server-cert\n            key: tls.crt\n        keySecret:\n          name: metrics-server-cert\n          key: tls.key\n        {{ \"{{- else }}\" }}\n        # Development/Test mode (insecure configuration)\n        insecureSkipVerify: true\n        {{ \"{{- end }}\" }}\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n{{ \"{{- end }}\" }}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/service.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhook\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Service{}\n\n// Service scaffolds the Service for webhooks in the Helm chart\ntype Service struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Service) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"webhook\", \"service.yaml\")\n\t}\n\n\tf.TemplateBody = webhookServiceTemplate\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst webhookServiceTemplate = `{{` + \"`\" + `{{- if .Values.webhook.enable }}` + \"`\" + `}}\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .ProjectName }}-webhook-service\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart-templates/webhook/webhook.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage webhook\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Template{}\n\n// Template scaffolds both MutatingWebhookConfiguration and ValidatingWebhookConfiguration for the Helm chart\ntype Template struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\tMutatingWebhooks   []DataWebhook\n\tValidatingWebhooks []DataWebhook\n}\n\n// SetTemplateDefaults sets default configuration for the webhook template\nfunc (f *Template) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"templates\", \"webhook\", \"webhooks.yaml\")\n\t}\n\n\tf.TemplateBody = webhookTemplate\n\tf.IfExistsAction = machinery.OverwriteFile\n\treturn nil\n}\n\n// DataWebhook helps generate manifests based on the data gathered from the kustomize files\ntype DataWebhook struct {\n\tServiceName             string\n\tName                    string\n\tPath                    string\n\tType                    string\n\tFailurePolicy           string\n\tSideEffects             string\n\tAdmissionReviewVersions []string\n\tRules                   []DataWebhookRule\n}\n\n// DataWebhookRule helps generate manifests based on the data gathered from the kustomize files\ntype DataWebhookRule struct {\n\tOperations  []string\n\tAPIGroups   []string\n\tAPIVersions []string\n\tResources   []string\n}\n\nconst webhookTemplate = `{{` + \"`\" + `{{- if .Values.webhook.enable }}` + \"`\" + `}}\n\n{{- if .MutatingWebhooks }}\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: {{ .ProjectName }}-mutating-webhook-configuration\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  annotations:\n    {{` + \"`\" + `{{- if .Values.certmanager.enable }}` + \"`\" + `}}\n    cert-manager.io/inject-ca-from: \"{{` + \"`\" + `{{ $.Release.Namespace }}` + \"`\" + `}}/serving-cert\"\n    {{` + \"`\" + `{{- end }}` + \"`\" + `}}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\nwebhooks:\n  {{- range .MutatingWebhooks }}\n  - name: {{ .Name }}\n    clientConfig:\n      service:\n        name: {{ .ServiceName }}\n        namespace: {{ \"{{ .Release.Namespace }}\" }}\n        path: {{ .Path }}\n    failurePolicy: {{ .FailurePolicy }}\n    sideEffects: {{ .SideEffects }}\n    admissionReviewVersions:\n      {{- range .AdmissionReviewVersions }}\n      - {{ . }}\n      {{- end }}\n    rules:\n      {{- range .Rules }}\n      - operations:\n          {{- range .Operations }}\n          - {{ . }}\n          {{- end }}\n        apiGroups:\n          {{- range .APIGroups }}\n          - {{ . }}\n          {{- end }}\n        apiVersions:\n          {{- range .APIVersions }}\n          - {{ . }}\n          {{- end }}\n        resources:\n          {{- range .Resources }}\n          - {{ . }}\n          {{- end }}\n      {{- end -}}\n  {{- end }}\n{{- end }}\n{{- if and .MutatingWebhooks .ValidatingWebhooks }}\n---\n{{- end }}\n{{- if .ValidatingWebhooks }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: {{ .ProjectName }}-validating-webhook-configuration\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\n  annotations:\n    {{` + \"`\" + `{{- if .Values.certmanager.enable }}` + \"`\" + `}}\n    cert-manager.io/inject-ca-from: \"{{` + \"`\" + `{{ $.Release.Namespace }}` + \"`\" + `}}/serving-cert\"\n    {{` + \"`\" + `{{- end }}` + \"`\" + `}}\n  labels:\n    {{ \"{{- include \\\"chart.labels\\\" . | nindent 4 }}\" }}\nwebhooks:\n  {{- range .ValidatingWebhooks }}\n  - name: {{ .Name }}\n    clientConfig:\n      service:\n        name: {{ .ServiceName }}\n        namespace: {{ \"{{ .Release.Namespace }}\" }}\n        path: {{ .Path }}\n    failurePolicy: {{ .FailurePolicy }}\n    sideEffects: {{ .SideEffects }}\n    admissionReviewVersions:\n      {{- range .AdmissionReviewVersions }}\n      - {{ . }}\n      {{- end }}\n    rules:\n      {{- range .Rules }}\n      - operations:\n          {{- range .Operations }}\n          - {{ . }}\n          {{- end }}\n        apiGroups:\n          {{- range .APIGroups }}\n          - {{ . }}\n          {{- end }}\n        apiVersions:\n          {{- range .APIVersions }}\n          - {{ . }}\n          {{- end }}\n        resources:\n          {{- range .Resources }}\n          - {{ . }}\n          {{- end }}\n      {{- end }}\n  {{- end }}\n{{- end }}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/chart.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmChart{}\n\n// HelmChart scaffolds a file that defines the Helm chart structure\ntype HelmChart struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmChart) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"Chart.yaml\")\n\t}\n\n\tf.TemplateBody = helmChartTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst helmChartTemplate = `apiVersion: v2\nname: {{ .ProjectName }}\ndescription: A Helm chart to distribute the project {{ .ProjectName }}\ntype: application\nversion: 0.1.0\nappVersion: \"0.1.0\"\nicon: \"https://example.com/icon.png\"\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/github/test_chart.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmChartCI{}\n\n// HelmChartCI scaffolds the GitHub Action for testing Helm charts\ntype HelmChartCI struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmChartCI) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"test-chart.yml\")\n\t}\n\n\tf.TemplateBody = testChartTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\n//nolint:lll\nconst testChartTemplate = `name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare {{ .ProjectName }}\n        run: |\n          go mod tidy\n          make docker-build IMG={{ .ProjectName }}:v0.1.0\n          kind load docker-image {{ .ProjectName }}:v0.1.0\n\n      - name: Install Helm\n        run: |\n          curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash\n\n      - name: Verify Helm installation\n        run: helm version\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n# TODO: Uncomment if cert-manager is enabled\n#      - name: Install cert-manager via Helm\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true\n#\n#      - name: Wait for cert-manager to be ready\n#        run: |\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector\n#          kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n#\n#      - name: Install Prometheus via Helm\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus prometheus-community/prometheus --namespace monitoring --create-namespace\n#\n#      - name: Wait for Prometheus to be ready\n#        run: |\n#          kubectl wait --namespace monitoring --for=condition=available --timeout=300s deployment/prometheus-server\n\n      - name: Install Helm chart for project\n        run: |\n          helm install my-release ./dist/chart --create-namespace --namespace {{ .ProjectName }}-system\n\n      - name: Check Helm release status\n        run: |\n          helm status my-release --namespace {{ .ProjectName }}-system\n\n# TODO: Uncomment if prometheus.enabled is set to true to confirm that the ServiceMonitor gets created\n#      - name: Check Presence of ServiceMonitor\n#        run: |\n#          kubectl wait --namespace {{ .ProjectName }}-system --for=jsonpath='{.kind}'=ServiceMonitor servicemonitor/{{ .ProjectName }}-controller-manager-metrics-monitor\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/helmignore.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmIgnore{}\n\n// HelmIgnore scaffolds a file that defines the .helmignore for Helm packaging\ntype HelmIgnore struct {\n\tmachinery.TemplateMixin\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmIgnore) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \".helmignore\")\n\t}\n\n\tf.TemplateBody = helmIgnoreTemplate\n\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst helmIgnoreTemplate = `# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v1alpha/scaffolds/internal/templates/values.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmValues{}\n\n// HelmValues scaffolds a file that defines the values.yaml structure for the Helm chart\ntype HelmValues struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// DeployImages stores the images used for the DeployImage plugin\n\tDeployImages map[string]string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n\t// HasWebhooks is true when webhooks were found in the config\n\tHasWebhooks bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmValues) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\"dist\", \"chart\", \"values.yaml\")\n\t}\n\tf.TemplateBody = helmValuesTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst helmValuesTemplate = `# [MANAGER]: Manager Deployment Configurations\ncontrollerManager:\n  replicas: 1\n  container:\n    image:\n      repository: controller\n      tag: latest\n    imagePullPolicy: IfNotPresent\n    args:\n      - \"--leader-elect\"\n      - \"--metrics-bind-address=:8443\"\n      - \"--health-probe-bind-address=:8081\"\n    resources:\n      limits:\n        cpu: 500m\n        memory: 128Mi\n      requests:\n        cpu: 10m\n        memory: 64Mi\n    livenessProbe:\n      initialDelaySeconds: 15\n      periodSeconds: 20\n      httpGet:\n        path: /healthz\n        port: 8081\n    readinessProbe:\n      initialDelaySeconds: 5\n      periodSeconds: 10\n      httpGet:\n        path: /readyz\n        port: 8081\n    {{- if .DeployImages }}\n    env:\n    {{- range $kind, $image := .DeployImages }}\n      {{ $kind }}_IMAGE: {{ $image }}\n    {{- end }}\n    {{- end }}\n    securityContext:\n      allowPrivilegeEscalation: false\n      capabilities:\n        drop:\n          - \"ALL\"\n  securityContext:\n    runAsNonRoot: true\n    seccompProfile:\n      type: RuntimeDefault\n  terminationGracePeriodSeconds: 10\n  serviceAccountName: {{ .ProjectName }}-controller-manager\n\n# [RBAC]: To enable RBAC (Permissions) configurations\nrbac:\n  enable: true\n\n# [CRDs]: To enable the CRDs\ncrd:\n  # This option determines whether the CRDs are included\n  # in the installation process.\n  enable: true\n\n  # Enabling this option adds the \"helm.sh/resource-policy\": keep\n  # annotation to the CRD, ensuring it remains installed even when\n  # the Helm release is uninstalled.\n  # NOTE: Removing the CRDs will also remove all cert-manager CR(s)\n  # (Certificates, Issuers, ...) due to garbage collection.\n  keep: true\n\n# [METRICS]: Set to true to generate manifests for exporting metrics.\n# To disable metrics export set false, and ensure that the\n# ControllerManager argument \"--metrics-bind-address=:8443\" is removed.\nmetrics:\n  enable: true\n{{ if .HasWebhooks }}\n# [WEBHOOKS]: Webhooks configuration\n# The following configuration is automatically generated from the manifests\n# generated by controller-gen. To update run 'make manifests' and\n# the edit command with the '--force' flag\nwebhook:\n  enable: true\n{{ end }}\n# [PROMETHEUS]: To enable a ServiceMonitor to export metrics to Prometheus set true\nprometheus:\n  enable: false\n\n# [CERT-MANAGER]: To enable cert-manager injection to webhooks set true\ncertmanager:\n  enable: {{ .HasWebhooks }}\n\n# [NETWORK POLICIES]: To enable NetworkPolicies set true\nnetworkPolicy:\n  enable: false\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/edit.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\t\"go.yaml.in/yaml/v3\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds\"\n)\n\nconst (\n\t// DefaultManifestsFile is the default path for kustomize output manifests\n\tDefaultManifestsFile = \"dist/install.yaml\"\n\t// DefaultOutputDir is the default output directory for Helm charts\n\tDefaultOutputDir = \"dist\"\n\t// v1AlphaPluginKey is the deprecated v1-alpha plugin key\n\tv1AlphaPluginKey = \"helm.kubebuilder.io/v1-alpha\"\n)\n\nvar _ plugin.EditSubcommand = &editSubcommand{}\n\ntype editSubcommand struct {\n\tconfig        config.Config\n\tforce         bool\n\tmanifestsFile string\n\toutputDir     string\n}\n\n//nolint:lll\nfunc (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {\n\tsubcmdMeta.Description = `Generate a Helm chart from your project's kustomize output.\n\nParses 'make build-installer' output (dist/install.yaml) and generates chart to allow easy\ndistribution of your project. When enabled, adds Helm helpers targets to Makefile`\n\n\tsubcmdMeta.Examples = fmt.Sprintf(`# Generate Helm chart from default manifests (dist/install.yaml) to default output (dist/)\n  %[1]s edit --plugins=%[2]s\n\n# Generate Helm chart and overwrite existing files (useful for updates)\n  %[1]s edit --plugins=%[2]s --force\n\n# Generate Helm chart from a custom manifests file\n  %[1]s edit --plugins=%[2]s --manifests=path/to/custom-install.yaml\n\n# Generate Helm chart to a custom output directory\n  %[1]s edit --plugins=%[2]s --output-dir=charts\n\n# Generate from custom manifests to custom output directory\n  %[1]s edit --plugins=%[2]s --manifests=manifests/install.yaml --output-dir=helm-charts\n\n# Typical workflow:\n  make build-installer  # Generate dist/install.yaml with latest changes\n  %[1]s edit --plugins=%[2]s  # Generate/update Helm chart in dist/chart/\n\n**NOTE**: Chart.yaml is never overwritten (contains user-managed version info).\nWithout --force, the plugin also preserves values.yaml, NOTES.txt, _helpers.tpl, .helmignore,\nand .github/workflows/test-chart.yml.\nAll other template files in templates/ are always regenerated to match your current\nkustomize output. Use --force to regenerate all files except Chart.yaml.\n\nThe generated chart structure mirrors your config/ directory:\n<output>/chart/\n├── Chart.yaml\n├── values.yaml\n├── .helmignore\n└── templates/\n    ├── NOTES.txt\n    ├── _helpers.tpl\n    ├── rbac/\n    ├── manager/\n    ├── webhook/\n    └── ...\n`, cliMeta.CommandName, plugin.KeyFor(Plugin{}))\n}\n\nfunc (p *editSubcommand) BindFlags(fs *pflag.FlagSet) {\n\tfs.BoolVar(&p.force, \"force\", false, \"if true, regenerates all the files\")\n\tfs.StringVar(&p.manifestsFile, \"manifests\", DefaultManifestsFile,\n\t\t\"path to the YAML file containing Kubernetes manifests from kustomize output\")\n\tfs.StringVar(&p.outputDir, \"output-dir\", DefaultOutputDir, \"output directory for the generated Helm chart\")\n}\n\nfunc (p *editSubcommand) InjectConfig(c config.Config) error {\n\tp.config = c\n\treturn nil\n}\n\nfunc (p *editSubcommand) Scaffold(fs machinery.Filesystem) error {\n\t// If using default manifests file, ensure it exists by running make build-installer\n\tif p.manifestsFile == DefaultManifestsFile {\n\t\tif err := p.ensureManifestsExist(); err != nil {\n\t\t\tslog.Warn(\"Failed to generate default manifests file\", \"error\", err, \"file\", p.manifestsFile)\n\t\t}\n\t}\n\n\tscaffolder := scaffolds.NewKustomizeHelmScaffolder(p.config, p.force, p.manifestsFile, p.outputDir)\n\tscaffolder.InjectFS(fs)\n\terr := scaffolder.Scaffold()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error scaffolding Helm chart: %w\", err)\n\t}\n\n\t// Remove deprecated v1-alpha plugin entry from PROJECT file\n\t// This must happen in Scaffold (before config is saved) to be persisted\n\tp.removeV1AlphaPluginEntry()\n\n\t// Save plugin config to PROJECT file\n\tkey := plugin.GetPluginKeyForConfig(p.config.GetPluginChain(), Plugin{})\n\tcanonicalKey := plugin.KeyFor(Plugin{})\n\tcfg := pluginConfig{}\n\tisFirstRun := false\n\tif err = p.config.DecodePluginConfig(key, &cfg); err != nil {\n\t\tswitch {\n\t\tcase errors.As(err, &config.UnsupportedFieldError{}):\n\t\t\t// Config version doesn't support plugin metadata\n\t\t\treturn nil\n\t\tcase errors.As(err, &config.PluginKeyNotFoundError{}):\n\t\t\t// This is the first time the plugin is run\n\t\t\tisFirstRun = true\n\t\t\tif key != canonicalKey {\n\t\t\t\tif err2 := p.config.DecodePluginConfig(canonicalKey, &cfg); err2 != nil {\n\t\t\t\t\tif errors.As(err2, &config.UnsupportedFieldError{}) {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.As(err2, &config.PluginKeyNotFoundError{}) {\n\t\t\t\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err2)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Found config under canonical key, not first run\n\t\t\t\t\tisFirstRun = false\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error decoding plugin configuration: %w\", err)\n\t\t}\n\t}\n\n\t// Update configuration with current parameters\n\tcfg.ManifestsFile = p.manifestsFile\n\tcfg.OutputDir = p.outputDir\n\n\tif err = p.config.EncodePluginConfig(key, cfg); err != nil {\n\t\treturn fmt.Errorf(\"error encoding plugin configuration: %w\", err)\n\t}\n\n\t// Add Helm deployment targets to Makefile only on first run\n\tif isFirstRun {\n\t\tslog.Info(\"adding Helm deployment targets to Makefile...\")\n\t\t// Extract namespace from manifests for accurate Makefile generation\n\t\tnamespace := p.extractNamespaceFromManifests()\n\t\tif err := p.addHelmMakefileTargets(namespace); err != nil {\n\t\t\tslog.Warn(\"failed to add Helm targets to Makefile\", \"error\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ensureManifestsExist runs make build-installer to generate the default manifests file\nfunc (p *editSubcommand) ensureManifestsExist() error {\n\tslog.Info(\"Generating default manifests file\", \"file\", p.manifestsFile)\n\n\t// Run the required make targets to generate the manifests file\n\ttargets := []string{\"manifests\", \"generate\", \"build-installer\"}\n\tfor _, target := range targets {\n\t\tif err := util.RunCmd(fmt.Sprintf(\"Running make %s\", target), \"make\", target); err != nil {\n\t\t\treturn fmt.Errorf(\"make %s failed: %w\", target, err)\n\t\t}\n\t}\n\n\t// Verify the file was created\n\tif _, err := os.Stat(p.manifestsFile); err != nil {\n\t\treturn fmt.Errorf(\"manifests file %s was not created: %w\", p.manifestsFile, err)\n\t}\n\n\tslog.Info(\"Successfully generated manifests file\", \"file\", p.manifestsFile)\n\treturn nil\n}\n\n// PostScaffold automatically uncomments cert-manager installation when webhooks are present\nfunc (p *editSubcommand) PostScaffold() error {\n\thasWebhooks := hasWebhooksWith(p.config)\n\n\tif hasWebhooks {\n\t\tworkflowFile := filepath.Join(\".github\", \"workflows\", \"test-chart.yml\")\n\t\tif _, err := os.Stat(workflowFile); err != nil {\n\t\t\tslog.Info(\n\t\t\t\t\"Workflow file not found, unable to uncomment cert-manager installation\",\n\t\t\t\t\"error\", err,\n\t\t\t\t\"file\", workflowFile,\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\ttarget := `\n#      - name: Install cert-manager via Helm (wait for readiness)\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager \\\n#            --namespace cert-manager \\\n#            --create-namespace \\\n#            --set crds.enabled=true \\\n#            --wait \\\n#            --timeout 300s`\n\t\tif err := util.UncommentCode(workflowFile, target, \"#\"); err != nil {\n\t\t\thasUncommented, errCheck := util.HasFileContentWith(workflowFile, \"- name: Install cert-manager via Helm\")\n\t\t\tif !hasUncommented || errCheck != nil {\n\t\t\t\tslog.Warn(\"Failed to uncomment cert-manager installation in workflow file\", \"error\", err, \"file\", workflowFile)\n\t\t\t}\n\t\t} else {\n\t\t\ttarget = `# TODO: Uncomment if cert-manager is enabled`\n\t\t\t_ = util.ReplaceInFile(workflowFile, target, \"\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// addHelmMakefileTargets appends Helm deployment targets to the Makefile if they don't already exist\nfunc (p *editSubcommand) addHelmMakefileTargets(namespace string) error {\n\tmakefilePath := \"Makefile\"\n\tif _, err := os.Stat(makefilePath); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"makefile not found\")\n\t}\n\n\t// Get the Helm Makefile targets\n\thelmTargets := getHelmMakefileTargets(p.config.GetProjectName(), namespace, p.outputDir)\n\n\t// Append the targets if they don't already exist\n\tif err := util.AppendCodeIfNotExist(makefilePath, helmTargets); err != nil {\n\t\treturn fmt.Errorf(\"failed to append Helm targets to Makefile: %w\", err)\n\t}\n\n\tslog.Info(\"added Helm deployment targets to Makefile\",\n\t\t\"targets\", \"helm-deploy, helm-uninstall, helm-status, helm-history, helm-rollback\")\n\treturn nil\n}\n\n// extractNamespaceFromManifests parses the manifests file to extract the manager namespace.\n// Returns projectName-system if manifests don't exist or namespace not found.\nfunc (p *editSubcommand) extractNamespaceFromManifests() string {\n\t// Default to project-name-system pattern\n\tdefaultNamespace := p.config.GetProjectName() + \"-system\"\n\n\t// If manifests file doesn't exist, use default\n\tif _, err := os.Stat(p.manifestsFile); os.IsNotExist(err) {\n\t\treturn defaultNamespace\n\t}\n\n\t// Parse the manifests to get the namespace\n\tfile, err := os.Open(p.manifestsFile)\n\tif err != nil {\n\t\treturn defaultNamespace\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\t// Parse YAML documents looking for the manager Deployment\n\tdecoder := yaml.NewDecoder(file)\n\tfor {\n\t\tvar doc map[string]any\n\t\tif err := decoder.Decode(&doc); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check if this is a Deployment (manager)\n\t\tif kind, ok := doc[\"kind\"].(string); ok && kind == \"Deployment\" {\n\t\t\tif metadata, ok := doc[\"metadata\"].(map[string]any); ok {\n\t\t\t\t// Check if it's the manager deployment\n\t\t\t\tif name, ok := metadata[\"name\"].(string); ok && strings.Contains(name, \"controller-manager\") {\n\t\t\t\t\t// Extract namespace from the manager Deployment\n\t\t\t\t\tif namespace, ok := metadata[\"namespace\"].(string); ok && namespace != \"\" {\n\t\t\t\t\t\treturn namespace\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fallback to default if manager Deployment not found\n\treturn defaultNamespace\n}\n\n// getHelmMakefileTargets returns the Helm Makefile targets as a string\n// following the same patterns as the existing Makefile deployment section\nfunc getHelmMakefileTargets(projectName, namespace, outputDir string) string {\n\tif outputDir == \"\" {\n\t\toutputDir = \"dist\"\n\t}\n\n\t// Use the project name as default for release name\n\trelease := projectName\n\n\treturn helmMakefileTemplate(namespace, release, outputDir)\n}\n\n// helmMakefileTemplate returns the Helm deployment section template\n// This follows the same pattern as the Kustomize deployment section in the Go plugin\nconst helmMakefileTemplateFormat = `\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= %s\n## Name of the Helm release\nHELM_RELEASE ?= %s\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= %s/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n`\n\nfunc helmMakefileTemplate(namespace, release, outputDir string) string {\n\treturn fmt.Sprintf(helmMakefileTemplateFormat, namespace, release, outputDir)\n}\n\nfunc hasWebhooksWith(c config.Config) bool {\n\tresources, err := c.GetResources()\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tfor _, res := range resources {\n\t\tif res.HasDefaultingWebhook() || res.HasValidationWebhook() || res.HasConversionWebhook() {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// removeV1AlphaPluginEntry removes the deprecated helm.kubebuilder.io/v1-alpha plugin entry.\n// This must be called from Scaffold (before config is saved) for changes to be persisted.\nfunc (p *editSubcommand) removeV1AlphaPluginEntry() {\n\t// Only attempt to remove if using v3 config (which supports plugin configs)\n\tcfg, ok := p.config.(*cfgv3.Cfg)\n\tif !ok {\n\t\treturn\n\t}\n\n\t// Check if v1-alpha plugin entry exists\n\tif cfg.Plugins == nil {\n\t\treturn\n\t}\n\n\tif _, exists := cfg.Plugins[v1AlphaPluginKey]; exists {\n\t\tdelete(cfg.Plugins, v1AlphaPluginKey)\n\t\tslog.Info(\"removed deprecated v1-alpha plugin entry\")\n\t}\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/edit_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/spf13/pflag\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n)\n\nvar _ = Describe(\"editSubcommand\", func() {\n\tvar (\n\t\teditCmd *editSubcommand\n\t\tcfg     config.Config\n\t\tfs      machinery.Filesystem\n\t)\n\n\tBeforeEach(func() {\n\t\t// Create test config\n\t\tmemFs := afero.NewMemMapFs()\n\t\tfs = machinery.Filesystem{FS: memFs}\n\t\tstore := yaml.New(fs)\n\n\t\t// Create a basic PROJECT file\n\t\tprojectContent := `domain: example.com\nlayout:\n- go.kubebuilder.io/v4\nprojectName: test-project\nrepo: example.com/test-project\nversion: \"3\"\n`\n\t\terr := afero.WriteFile(memFs, \"PROJECT\", []byte(projectContent), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\terr = store.LoadFrom(\"PROJECT\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tcfg = store.Config()\n\n\t\t// Create edit subcommand\n\t\teditCmd = &editSubcommand{\n\t\t\tconfig: cfg,\n\t\t}\n\t})\n\n\tContext(\"UpdateMetadata\", func() {\n\t\tIt(\"should set correct metadata\", func() {\n\t\t\tcliMeta := plugin.CLIMetadata{CommandName: \"kubebuilder\"}\n\t\t\tmeta := plugin.SubcommandMetadata{}\n\t\t\teditCmd.UpdateMetadata(cliMeta, &meta)\n\n\t\t\tExpect(meta.Description).To(ContainSubstring(\"Generate a Helm chart\"))\n\t\t\tExpect(meta.Description).To(ContainSubstring(\"kustomize\"))\n\t\t\tExpect(meta.Examples).NotTo(BeEmpty())\n\t\t})\n\t})\n\n\tContext(\"BindFlags\", func() {\n\t\tIt(\"should bind flags correctly\", func() {\n\t\t\tflagSet := pflag.NewFlagSet(\"test\", pflag.ContinueOnError)\n\t\t\teditCmd.BindFlags(flagSet)\n\n\t\t\t// Check that flags were added\n\t\t\tmanifestsFlag := flagSet.Lookup(\"manifests\")\n\t\t\tExpect(manifestsFlag).NotTo(BeNil())\n\t\t\tExpect(manifestsFlag.DefValue).To(Equal(DefaultManifestsFile))\n\n\t\t\toutputFlag := flagSet.Lookup(\"output-dir\")\n\t\t\tExpect(outputFlag).NotTo(BeNil())\n\t\t\tExpect(outputFlag.DefValue).To(Equal(DefaultOutputDir))\n\n\t\t\tforceFlag := flagSet.Lookup(\"force\")\n\t\t\tExpect(forceFlag).NotTo(BeNil())\n\t\t})\n\t})\n\n\tContext(\"InjectConfig\", func() {\n\t\tIt(\"should inject config correctly\", func() {\n\t\t\terr := editCmd.InjectConfig(cfg)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(editCmd.config).To(Equal(cfg))\n\t\t})\n\t})\n\n\tContext(\"hasWebhooksWith\", func() {\n\t\tIt(\"should return false for config without webhooks\", func() {\n\t\t\tresult := hasWebhooksWith(cfg)\n\t\t\tExpect(result).To(BeFalse())\n\t\t})\n\t})\n\n\tContext(\"removeV1AlphaPluginEntry\", func() {\n\t\tIt(\"should remove v1-alpha plugin entry if it exists\", func() {\n\t\t\t// Add v1-alpha plugin entry to config\n\t\t\terr := cfg.EncodePluginConfig(v1AlphaPluginKey, map[string]any{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify it exists\n\t\t\tvar v1AlphaCfg map[string]any\n\t\t\terr = cfg.DecodePluginConfig(v1AlphaPluginKey, &v1AlphaCfg)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Remove it\n\t\t\teditCmd.removeV1AlphaPluginEntry()\n\n\t\t\t// Verify it was removed from in-memory config\n\t\t\terr = cfg.DecodePluginConfig(v1AlphaPluginKey, &v1AlphaCfg)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"plugin key\"))\n\t\t})\n\n\t\tIt(\"should not error when v1-alpha entry does not exist\", func() {\n\t\t\t// Should not panic or error\n\t\t\teditCmd.removeV1AlphaPluginEntry()\n\t\t})\n\n\t\tIt(\"should not error when plugins map is nil\", func() {\n\t\t\t// Create a fresh config without any plugins\n\t\t\tmemFs := afero.NewMemMapFs()\n\t\t\tfreshFs := machinery.Filesystem{FS: memFs}\n\t\t\tstore := yaml.New(freshFs)\n\n\t\t\tprojectContent := `domain: example.com\nlayout:\n- go.kubebuilder.io/v4\nprojectName: test-project\nrepo: example.com/test-project\nversion: \"3\"\n`\n\t\t\terr := afero.WriteFile(memFs, \"PROJECT\", []byte(projectContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = store.LoadFrom(\"PROJECT\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tfreshCfg := store.Config()\n\t\t\tfreshEditCmd := &editSubcommand{config: freshCfg}\n\n\t\t\t// Should not panic or error\n\t\t\tfreshEditCmd.removeV1AlphaPluginEntry()\n\t\t})\n\n\t\tIt(\"should persist v1-alpha removal when config is saved\", func() {\n\t\t\t// Create a store to test actual persistence\n\t\t\tmemFs := afero.NewMemMapFs()\n\t\t\ttestFs := machinery.Filesystem{FS: memFs}\n\t\t\tstore := yaml.New(testFs)\n\n\t\t\t// Create PROJECT file with v1-alpha plugin entry\n\t\t\tprojectContent := `domain: example.com\nlayout:\n- go.kubebuilder.io/v4\nplugins:\n  helm.kubebuilder.io/v1-alpha: {}\nprojectName: test-project\nrepo: example.com/test-project\nversion: \"3\"\n`\n\t\t\terr := afero.WriteFile(memFs, \"PROJECT\", []byte(projectContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = store.LoadFrom(\"PROJECT\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\ttestCfg := store.Config()\n\t\t\ttestEditCmd := &editSubcommand{config: testCfg}\n\n\t\t\t// Verify v1-alpha entry exists before removal\n\t\t\tvar v1AlphaCfg map[string]any\n\t\t\terr = testCfg.DecodePluginConfig(v1AlphaPluginKey, &v1AlphaCfg)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Remove v1-alpha entry\n\t\t\ttestEditCmd.removeV1AlphaPluginEntry()\n\n\t\t\t// Save config to disk\n\t\t\terr = store.Save()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Reload config from disk\n\t\t\terr = store.LoadFrom(\"PROJECT\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\treloadedCfg := store.Config()\n\n\t\t\t// Verify v1-alpha entry is gone after reload\n\t\t\terr = reloadedCfg.DecodePluginConfig(v1AlphaPluginKey, &v1AlphaCfg)\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"plugin key\"))\n\t\t})\n\t})\n\n\tContext(\"PostScaffold\", func() {\n\t\tBeforeEach(func() {\n\t\t\t// Create the directory structure\n\t\t\terr := fs.FS.MkdirAll(\".github/workflows\", 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should not modify workflow file when no webhooks present\", func() {\n\t\t\t// Create test workflow file\n\t\t\tworkflowContent := `name: Test Chart\non:\n  push:\n    branches: [main]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n#      - name: Install cert-manager via Helm\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager \\\n#            --namespace cert-manager --create-namespace --set crds.enabled=true\n#\n#      - name: Wait for cert-manager to be ready\n#        run: |\n#          kubectl wait --namespace cert-manager --for=condition=available \\\n#            --timeout=300s deployment/cert-manager\n#          kubectl wait --namespace cert-manager --for=condition=available \\\n#            --timeout=300s deployment/cert-manager-cainjector\n#          kubectl wait --namespace cert-manager --for=condition=available \\\n#            --timeout=300s deployment/cert-manager-webhook\n`\n\t\t\tworkflowPath := filepath.Join(\".github\", \"workflows\", \"test-chart.yml\")\n\t\t\terr := afero.WriteFile(fs.FS, workflowPath, []byte(workflowContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = editCmd.PostScaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Content should remain unchanged\n\t\t\tcontent, err := afero.ReadFile(fs.FS, workflowPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(content)).To(ContainSubstring(\"#      - name: Install cert-manager via Helm\"))\n\t\t})\n\n\t\tIt(\"should handle missing workflow file gracefully\", func() {\n\t\t\teditCmd.config = cfg\n\t\t\terr := editCmd.PostScaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred()) // Should not error even if file doesn't exist\n\t\t})\n\t})\n\n\tContext(\"addHelmMakefileTargets\", func() {\n\t\tvar tmpDir string\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-makefile-test-*\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Change to temp directory\n\t\t\terr = os.Chdir(tmpDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\teditCmd.outputDir = DefaultOutputDir\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// Clean up temp directory\n\t\t\tif tmpDir != \"\" {\n\t\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should add Helm targets to Makefile when it exists\", func() {\n\t\t\t// Create a basic Makefile\n\t\t\tmakefileContent := `IMG ?= controller:latest\n\n##@ Development\n\n.PHONY: build\nbuild: ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n`\n\t\t\terr := os.WriteFile(\"Makefile\", []byte(makefileContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = editCmd.addHelmMakefileTargets(\"test-project-system\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify Helm targets were added\n\t\t\tcontent, err := os.ReadFile(\"Makefile\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcontentStr := string(content)\n\t\t\tExpect(contentStr).To(ContainSubstring(\"##@ Helm Deployment\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"## Helm binary to use for deploying the chart\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"HELM ?= helm\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"## Namespace to deploy the Helm release\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"HELM_NAMESPACE ?= test-project-system\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"## Name of the Helm release\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"HELM_RELEASE ?= test-project\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"## Path to the Helm chart directory\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"HELM_CHART_DIR ?= dist/chart\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"## Additional arguments to pass to helm commands\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"HELM_EXTRA_ARGS ?=\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: install-helm\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"install-helm: ## Install the latest version of Helm.\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: helm-deploy\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\n\t\t\t\t\"helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"--set manager.image.repository=$${IMG%:*}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"--set manager.image.tag=$${IMG##*:}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: helm-uninstall\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\n\t\t\t\t\"helm-uninstall: ## Uninstall the Helm release from the K8s cluster.\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: helm-status\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"helm-status: ## Show Helm release status.\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: helm-history\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"helm-history: ## Show Helm release history.\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\".PHONY: helm-rollback\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"helm-rollback: ## Rollback to previous Helm release.\"))\n\t\t})\n\n\t\tIt(\"should not duplicate Helm targets if already present\", func() {\n\t\t\t// Create a Makefile that already has Helm targets (exact match to template)\n\t\t\tmakefileContent := `IMG ?= controller:latest\n\n.PHONY: build\nbuild: ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= test-project-system\n## Name of the Helm release\nHELM_RELEASE ?= test-project\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= dist/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n`\n\t\t\terr := os.WriteFile(\"Makefile\", []byte(makefileContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\terr = editCmd.addHelmMakefileTargets(\"test-project-system\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify targets were not duplicated\n\t\t\tcontent, err := os.ReadFile(\"Makefile\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Count occurrences of helm-deploy target\n\t\t\tcontentStr := string(content)\n\t\t\thelmDeployCount := 0\n\t\t\tfor i := 0; i < len(contentStr)-len(\"helm-deploy:\"); i++ {\n\t\t\t\tif contentStr[i:i+len(\"helm-deploy:\")] == \"helm-deploy:\" {\n\t\t\t\t\thelmDeployCount++\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(helmDeployCount).To(Equal(1)) // Should only appear once\n\t\t})\n\n\t\tIt(\"should return error when Makefile does not exist\", func() {\n\t\t\terr := editCmd.addHelmMakefileTargets(\"test-project-system\")\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(err.Error()).To(ContainSubstring(\"makefile not found\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/makefile_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n)\n\nvar _ = Describe(\"Helm Makefile Targets\", func() {\n\tvar (\n\t\ttmpDir       string\n\t\tmakefilePath string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-makefile-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tmakefilePath = filepath.Join(tmpDir, \"Makefile\")\n\n\t\t// Create a basic Makefile with some existing content\n\t\tinitialContent := `# Basic Makefile\n.PHONY: all\nall: build\n\n.PHONY: build\nbuild:\n\tgo build -o bin/manager cmd/main.go\n\n##@ Deployment\n.PHONY: deploy\ndeploy:\n\tkubectl apply -k config/default\n`\n\t\terr = os.WriteFile(makefilePath, []byte(initialContent), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tIt(\"should add Helm deployment targets without duplication\", func() {\n\t\t// Get the Helm targets\n\t\thelmTargets := getHelmMakefileTargets(\"test-project\", \"test-system\", \"dist\")\n\n\t\tBy(\"appending Helm targets for the first time\")\n\t\terr := util.AppendCodeIfNotExist(makefilePath, helmTargets)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Read the Makefile\n\t\tcontent, err := os.ReadFile(makefilePath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tmakefileContent := string(content)\n\n\t\tBy(\"verifying Helm section was added\")\n\t\tExpect(makefileContent).To(ContainSubstring(\"##@ Helm Deployment\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\"HELM ?= helm\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\".PHONY: install-helm\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\"install-helm: ## Install the latest version of Helm.\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\".PHONY: helm-deploy\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\"helm-deploy: install-helm ##\"))\n\t\tExpect(makefileContent).To(ContainSubstring(\".PHONY: helm-uninstall\"))\n\n\t\t// Count occurrences\n\t\thelmSectionCount := strings.Count(makefileContent, \"##@ Helm Deployment\")\n\t\thelmDeployCount := strings.Count(makefileContent, \".PHONY: helm-deploy\")\n\t\thelmInstallCount := strings.Count(makefileContent, \"install-helm: ## Install the latest version of Helm.\")\n\t\tExpect(helmSectionCount).To(Equal(1), \"Helm section should appear exactly once\")\n\t\tExpect(helmDeployCount).To(Equal(1), \"helm-deploy should appear exactly once\")\n\t\tExpect(helmInstallCount).To(Equal(1), \"install-helm target should appear exactly once\")\n\n\t\tBy(\"appending Helm targets again (should not duplicate)\")\n\t\terr = util.AppendCodeIfNotExist(makefilePath, helmTargets)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Read the Makefile again\n\t\tcontent2, err := os.ReadFile(makefilePath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tmakefileContent2 := string(content2)\n\n\t\tBy(\"verifying no duplication occurred\")\n\t\thelmSectionCount2 := strings.Count(makefileContent2, \"##@ Helm Deployment\")\n\t\thelmDeployCount2 := strings.Count(makefileContent2, \".PHONY: helm-deploy\")\n\t\thelmInstallCount2 := strings.Count(makefileContent2, \"install-helm: ## Install the latest version of Helm.\")\n\t\tExpect(helmSectionCount2).To(Equal(1), \"Helm section should still appear exactly once\")\n\t\tExpect(helmDeployCount2).To(Equal(1), \"helm-deploy should still appear exactly once\")\n\t\tExpect(helmInstallCount2).To(Equal(1), \"install-helm target should still appear exactly once\")\n\n\t\t// Verify content is identical (no duplication)\n\t\tExpect(makefileContent2).To(Equal(makefileContent), \"Makefile should be unchanged after second append\")\n\t})\n\n\tIt(\"should generate correct Helm targets template\", func() {\n\t\thelmTargets := getHelmMakefileTargets(\"my-project\", \"my-project-system\", \"dist\")\n\n\t\tBy(\"verifying template contains expected sections\")\n\t\tExpect(helmTargets).To(ContainSubstring(\"##@ Helm Deployment\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"HELM_NAMESPACE ?= my-project-system\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"HELM_RELEASE ?= my-project\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"HELM_CHART_DIR ?= dist/chart\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"install-helm: ## Install the latest version of Helm.\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"helm-deploy: install-helm ##\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"helm-uninstall:\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"helm-status:\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"helm-history:\"))\n\t\tExpect(helmTargets).To(ContainSubstring(\"helm-rollback:\"))\n\t})\n\n\tIt(\"should handle custom output directory\", func() {\n\t\thelmTargets := getHelmMakefileTargets(\"test-project\", \"test-system\", \"custom-charts\")\n\n\t\tBy(\"verifying custom directory is used\")\n\t\tExpect(helmTargets).To(ContainSubstring(\"HELM_CHART_DIR ?= custom-charts/chart\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/plugin.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/model/stage\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n)\n\nconst pluginName = \"helm.\" + plugins.DefaultNameQualifier\n\nvar (\n\tpluginVersion            = plugin.Version{Number: 2, Stage: stage.Alpha}\n\tsupportedProjectVersions = []config.Version{cfgv3.Version}\n)\n\n// Plugin implements the plugin.Full interface\ntype Plugin struct {\n\teditSubcommand\n}\n\nvar _ plugin.Edit = Plugin{}\n\n// PluginConfig defines the structure that will be used to track the data\ntype pluginConfig struct {\n\tManifestsFile string `json:\"manifests,omitempty\"`\n\tOutputDir     string `json:\"output,omitempty\"`\n}\n\n// Name returns the name of the plugin\nfunc (Plugin) Name() string { return pluginName }\n\n// Version returns the version of the Helm plugin\nfunc (Plugin) Version() plugin.Version { return pluginVersion }\n\n// SupportedProjectVersions returns an array with all project versions supported by the plugin\nfunc (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions }\n\n// GetEditSubcommand will return the subcommand which is responsible for adding and/or edit a helm chart\nfunc (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand }\n\n// Description returns a short description of the plugin\nfunc (Plugin) Description() string {\n\treturn \"Generates a Helm chart for project distribution\"\n}\n\n// DeprecationWarning define the deprecation message or return empty when plugin is not deprecated\nfunc (p Plugin) DeprecationWarning() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/plugin_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n)\n\nvar _ = Describe(\"Plugin\", func() {\n\tvar p Plugin\n\n\tBeforeEach(func() {\n\t\tp = Plugin{}\n\t})\n\n\tContext(\"Name\", func() {\n\t\tIt(\"should return the correct plugin name\", func() {\n\t\t\tExpect(p.Name()).To(Equal(\"helm.kubebuilder.io\"))\n\t\t})\n\t})\n\n\tContext(\"Version\", func() {\n\t\tIt(\"should return version 2-alpha\", func() {\n\t\t\tversion := p.Version()\n\t\t\tExpect(version.Number).To(Equal(2))\n\t\t\tExpect(version.Stage.String()).To(Equal(\"alpha\"))\n\t\t})\n\t})\n\n\tContext(\"SupportedProjectVersions\", func() {\n\t\tIt(\"should support project version 3\", func() {\n\t\t\tversions := p.SupportedProjectVersions()\n\t\t\texpectedVersion := config.Version{Number: 3}\n\t\t\tExpect(versions).To(ContainElement(expectedVersion))\n\t\t})\n\t})\n\n\tContext(\"GetEditSubcommand\", func() {\n\t\tIt(\"should return an edit subcommand\", func() {\n\t\t\tsubcommand := p.GetEditSubcommand()\n\t\t\tExpect(subcommand).NotTo(BeNil())\n\t\t})\n\t})\n\n\tContext(\"DeprecationWarning\", func() {\n\t\tIt(\"should return empty string since v2-alpha is not deprecated\", func() {\n\t\t\twarning := p.DeprecationWarning()\n\t\t\tExpect(warning).To(BeEmpty())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/chart_generation_integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\thelmChartLoader \"helm.sh/helm/v3/pkg/chart/loader\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"Chart Generation Integration Tests\", func() {\n\tvar (\n\t\tfs             machinery.Filesystem\n\t\ttmpDir         string\n\t\tmanifestsFile  string\n\t\toutputDir      string\n\t\tprojectConfig  config.Config\n\t\tscaffolderBase *editKustomizeScaffolder\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-chart-gen-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\n\t\tprojectConfig = cfgv3.New()\n\t\tprojectConfig.SetProjectName(\"test-project\")\n\t\tprojectConfig.SetDomain(\"example.io\")\n\n\t\tmanifestsFile = filepath.Join(tmpDir, \"dist\", \"install.yaml\")\n\t\toutputDir = \"dist\"\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tContext(\"Basic Functionality\", func() {\n\t\tIt(\"should generate valid helm chart with dynamic templates\", func() {\n\t\t\tkustomizeYAML := createKustomizeWithCRDAndRBAC(\"test-project\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     outputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\")\n\n\t\t\tBy(\"verifying templates directory structure matches config/ structure\")\n\t\t\texpectedDirs := []string{\n\t\t\t\t\"templates/manager\",\n\t\t\t\t\"templates/rbac\",\n\t\t\t\t\"templates/crd\",\n\t\t\t}\n\t\t\tfor _, dir := range expectedDirs {\n\t\t\t\tdirPath := filepath.Join(chartPath, dir)\n\t\t\t\tinfo, err := os.Stat(dirPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred(), \"Directory %s should exist\", dir)\n\t\t\t\tExpect(info.IsDir()).To(BeTrue())\n\t\t\t}\n\n\t\t\tBy(\"verifying manager deployment template exists\")\n\t\t\tmanagerTemplate := filepath.Join(chartPath, \"templates\", \"manager\", \"manager.yaml\")\n\t\t\t_, err = os.Stat(managerTemplate)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying CRD templates exist\")\n\t\t\tcrdDir := filepath.Join(chartPath, \"templates\", \"crd\")\n\t\t\tfiles, err := afero.ReadDir(afero.NewOsFs(), crdDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).ToNot(BeEmpty())\n\n\t\t\tBy(\"verifying Chart.yaml exists and is valid\")\n\t\t\tchart, err := helmChartLoader.LoadDir(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(chart.Validate()).To(Succeed())\n\t\t\tExpect(chart.Name()).To(Equal(\"test-project\"))\n\n\t\t\tBy(\"verifying essential files exist\")\n\t\t\tessentialFiles := []string{\n\t\t\t\t\"Chart.yaml\",\n\t\t\t\t\"values.yaml\",\n\t\t\t\t\".helmignore\",\n\t\t\t\t\"templates/_helpers.tpl\",\n\t\t\t}\n\t\t\tfor _, file := range essentialFiles {\n\t\t\t\tfilePath := filepath.Join(chartPath, file)\n\t\t\t\t_, err := os.Stat(filePath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred(), \"File %s should exist\", file)\n\t\t\t}\n\t\t})\n\t})\n\n\tContext(\"Webhook and Cert-Manager Integration\", func() {\n\t\tIt(\"should generate webhook templates with cert-manager integration and proper templating\", func() {\n\t\t\tkustomizeYAML := createKustomizeWithWebhooksAndCertManager(\"e2e-test\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tprojectConfig.SetProjectName(\"e2e-test\")\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     outputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\")\n\n\t\t\tBy(\"verifying webhook directory exists\")\n\t\t\twebhookDir := filepath.Join(chartPath, \"templates\", \"webhook\")\n\t\t\tinfo, err := os.Stat(webhookDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(info.IsDir()).To(BeTrue())\n\n\t\t\tBy(\"verifying webhook configuration files exist\")\n\t\t\tfiles, err := afero.ReadDir(afero.NewOsFs(), webhookDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).ToNot(BeEmpty())\n\n\t\t\tBy(\"verifying webhook files contain webhook configurations\")\n\t\t\tfoundValidatingWebhook := false\n\t\t\tfor _, file := range files {\n\t\t\t\tif file.IsDir() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\twebhookFile := filepath.Join(webhookDir, file.Name())\n\t\t\t\tcontent, err := afero.ReadFile(afero.NewOsFs(), webhookFile)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\t\t\t\tif strings.Contains(contentStr, \"ValidatingWebhookConfiguration\") {\n\t\t\t\t\tfoundValidatingWebhook = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(foundValidatingWebhook).To(BeTrue(), \"Expected to find ValidatingWebhookConfiguration in webhook templates\")\n\n\t\t\tBy(\"verifying cert-manager templates exist\")\n\t\t\tcertManagerDir := filepath.Join(chartPath, \"templates\", \"cert-manager\")\n\t\t\tcertInfo, err := os.Stat(certManagerDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(certInfo.IsDir()).To(BeTrue())\n\n\t\t\tBy(\"verifying cert-manager is enabled in values.yaml\")\n\t\t\tvaluesPath := filepath.Join(chartPath, \"values.yaml\")\n\t\t\tvaluesContent, err := afero.ReadFile(afero.NewOsFs(), valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(valuesContent)).To(ContainSubstring(\"certManager:\"))\n\t\t\tExpect(string(valuesContent)).To(ContainSubstring(\"enable: true\"))\n\t\t})\n\t})\n\n\tContext(\"Chart Name Handling\", func() {\n\t\tIt(\"should use project name in helpers regardless of kustomize namePrefix\", func() {\n\t\t\t// Kustomize output with custom namePrefix\n\t\t\tkustomizeYAML := createKustomizeWithCustomPrefix(\"custom-prefix\", \"test-project\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tprojectConfig.SetProjectName(\"test-project\")\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     outputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\")\n\n\t\t\tBy(\"verifying _helpers.tpl uses project name, not kustomize prefix\")\n\t\t\thelpersContent, err := os.ReadFile(filepath.Join(chartPath, \"templates\", \"_helpers.tpl\"))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\thelpersStr := string(helpersContent)\n\n\t\t\t// Should contain project name-based templates\n\t\t\tExpect(helpersStr).To(ContainSubstring(`define \"test-project.name\"`))\n\t\t\tExpect(helpersStr).To(ContainSubstring(`define \"test-project.fullname\"`))\n\t\t\tExpect(helpersStr).To(ContainSubstring(`define \"test-project.resourceName\"`))\n\t\t\tExpect(helpersStr).To(ContainSubstring(`define \"test-project.namespaceName\"`))\n\n\t\t\t// Should NOT contain kustomize prefix in template definitions\n\t\t\tExpect(helpersStr).NotTo(ContainSubstring(`define \"custom-prefix.name\"`))\n\t\t\tExpect(helpersStr).NotTo(ContainSubstring(`define \"custom-prefix.fullname\"`))\n\n\t\t\tBy(\"verifying templates use project name helpers, not kustomize prefix\")\n\t\t\tmanagerContent, err := os.ReadFile(filepath.Join(chartPath, \"templates\", \"manager\", \"manager.yaml\"))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tmanagerStr := string(managerContent)\n\n\t\t\tExpect(managerStr).To(ContainSubstring(`include \"test-project`))\n\t\t\tExpect(managerStr).NotTo(ContainSubstring(`custom-prefix-controller-manager`),\n\t\t\t\t\"Manager template should not contain hardcoded kustomize prefix\")\n\t\t})\n\n\t\tIt(\"should properly template cert-manager resources when chart name is used\", func() {\n\t\t\tkustomizeYAML := createKustomizeWithWebhooksAndCertManager(\"e2e-test\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tprojectConfig.SetProjectName(\"e2e-test\")\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     outputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\")\n\t\t\tchartName := \"e2e-test\"\n\n\t\t\tBy(\"validating issuer name uses chartname.resourceName for 63-char safety\")\n\t\t\tissuerPath := filepath.Join(chartPath, \"templates\", \"cert-manager\", \"selfsigned-issuer.yaml\")\n\t\t\tcontent, err := afero.ReadFile(afero.NewOsFs(), issuerPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\n\t\t\texpected := `name: {{ include \"` + chartName + `.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}`\n\t\t\tExpect(contentStr).To(ContainSubstring(expected),\n\t\t\t\t\"Issuer name should use \"+chartName+\".resourceName template\")\n\t\t\tExpect(contentStr).NotTo(ContainSubstring(\"e2e-test-selfsigned-issuer\"),\n\t\t\t\t\"Issuer name should not be hardcoded to project name\")\n\n\t\t\tBy(\"validating certificate issuerRef uses chartname.resourceName\")\n\t\t\tcertManagerDir := filepath.Join(chartPath, \"templates\", \"cert-manager\")\n\t\t\tfiles, err := afero.ReadDir(afero.NewOsFs(), certManagerDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tfoundCertificate := false\n\t\t\tfor _, file := range files {\n\t\t\t\tif file.IsDir() || !strings.HasSuffix(file.Name(), \".yaml\") || file.Name() == \"selfsigned-issuer.yaml\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tcertPath := filepath.Join(certManagerDir, file.Name())\n\t\t\t\tcontent, err := afero.ReadFile(afero.NewOsFs(), certPath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\n\t\t\t\tif strings.Contains(contentStr, \"kind: Certificate\") {\n\t\t\t\t\tfoundCertificate = true\n\t\t\t\t\texpected := `name: {{ include \"` + chartName + `.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}`\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(expected),\n\t\t\t\t\t\t\"Certificate issuerRef should use \"+chartName+\".resourceName template in file \"+file.Name())\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(foundCertificate).To(BeTrue(), \"Expected to find at least one Certificate resource\")\n\n\t\t\tBy(\"validating cert-manager annotations use chartname.resourceName\")\n\t\t\t// Check webhook configurations\n\t\t\twebhookDir := filepath.Join(chartPath, \"templates\", \"webhook\")\n\t\t\tif exists, _ := afero.DirExists(afero.NewOsFs(), webhookDir); exists {\n\t\t\t\tfiles, err := afero.ReadDir(afero.NewOsFs(), webhookDir)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tfor _, file := range files {\n\t\t\t\t\tif file.IsDir() || !strings.HasSuffix(file.Name(), \".yaml\") {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\twebhookPath := filepath.Join(webhookDir, file.Name())\n\t\t\t\t\tcontent, err := afero.ReadFile(afero.NewOsFs(), webhookPath)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tcontentStr := string(content)\n\n\t\t\t\t\tif strings.Contains(contentStr, \"cert-manager.io/inject-ca-from\") {\n\t\t\t\t\t\texpected := `{{ include \"` + chartName + `.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}`\n\t\t\t\t\t\tExpect(contentStr).To(ContainSubstring(expected),\n\t\t\t\t\t\t\t\"cert-manager.io/inject-ca-from annotation should use \"+chartName+\".resourceName in \"+file.Name())\n\t\t\t\t\t\tExpect(contentStr).NotTo(ContainSubstring(\"e2e-test-serving-cert\"),\n\t\t\t\t\t\t\t\"cert-manager.io/inject-ca-from annotation should not be hardcoded in \"+file.Name())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tBy(\"validating app.kubernetes.io/name label uses chartname.name template\")\n\t\t\t// Check all cert-manager resources\n\t\t\tcertManagerFiles, err := afero.ReadDir(afero.NewOsFs(), certManagerDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tfor _, file := range certManagerFiles {\n\t\t\t\tif file.IsDir() || !strings.HasSuffix(file.Name(), \".yaml\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfilePath := filepath.Join(certManagerDir, file.Name())\n\t\t\t\tcontent, err := afero.ReadFile(afero.NewOsFs(), filePath)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontentStr := string(content)\n\n\t\t\t\tif strings.Contains(contentStr, \"app.kubernetes.io/name:\") {\n\t\t\t\t\tExpect(contentStr).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"`+chartName+`.name\" . }}`),\n\t\t\t\t\t\t\"app.kubernetes.io/name label should use \"+chartName+\".name template in \"+file.Name())\n\t\t\t\t\tExpect(contentStr).NotTo(ContainSubstring(\"app.kubernetes.io/name: e2e-test\"),\n\t\t\t\t\t\t\"app.kubernetes.io/name label should not be hardcoded in \"+file.Name())\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t})\n\n\tContext(\"Custom Output Directory\", func() {\n\t\tIt(\"should support custom output directory via --output-dir flag\", func() {\n\t\t\tkustomizeYAML := createBasicKustomizeOutput(\"test-project\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcustomOutputDir := \"custom-charts\"\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     customOutputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, customOutputDir, \"chart\")\n\n\t\t\tBy(\"verifying chart exists in custom directory\")\n\t\t\tinfo, err := os.Stat(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(info.IsDir()).To(BeTrue())\n\n\t\t\tBy(\"verifying Chart.yaml in custom directory\")\n\t\t\tchartFile := filepath.Join(chartPath, \"Chart.yaml\")\n\t\t\t_, err = os.Stat(chartFile)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\t})\n\n\tContext(\"Values Extraction\", func() {\n\t\tIt(\"should extract deployment configuration to values.yaml\", func() {\n\t\t\tkustomizeYAML := createKustomizeWithFullDeploymentConfig(\"test-project\")\n\t\t\terr := setupKustomizeFile(manifestsFile, kustomizeYAML)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\t\tconfig:        projectConfig,\n\t\t\t\tfs:            fs,\n\t\t\t\tmanifestsFile: manifestsFile,\n\t\t\t\toutputDir:     outputDir,\n\t\t\t}\n\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\")\n\t\t\tvaluesPath := filepath.Join(chartPath, \"values.yaml\")\n\t\t\tvaluesContent, err := os.ReadFile(valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tvaluesStr := string(valuesContent)\n\n\t\t\tBy(\"verifying image configuration is extracted\")\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"image:\"))\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"repository:\"))\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"tag:\"))\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"pullPolicy:\"))\n\n\t\t\tBy(\"verifying resources are extracted\")\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"resources:\"))\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"limits:\"))\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"requests:\"))\n\n\t\t\tBy(\"verifying security context is extracted\")\n\t\t\tExpect(valuesStr).To(ContainSubstring(\"securityContext:\"))\n\t\t})\n\t})\n})\n\n// Helper functions to create kustomize YAML outputs for different scenarios\n\nfunc createBasicKustomizeOutput(projectName string) string {\n\treturn `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + projectName + `-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + projectName + `-controller-manager\n  namespace: ` + projectName + `-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n    control-plane: controller-manager\n  name: ` + projectName + `-controller-manager\n  namespace: ` + projectName + `-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n`\n}\n\nfunc createKustomizeWithCRDAndRBAC(projectName string) string {\n\treturn createBasicKustomizeOutput(projectName) + `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: cronjobs.batch.tutorial.kubebuilder.io\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: ` + projectName + `-manager-role\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\nrules:\n- apiGroups: [\"*\"]\n  resources: [\"*\"]\n  verbs: [\"*\"]\n`\n}\n\nfunc createKustomizeWithWebhooks(projectName string) string {\n\treturn createBasicKustomizeOutput(projectName) + `---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + projectName + `-webhook-service\n  namespace: ` + projectName + `-system\nspec:\n  ports:\n  - port: 443\n    targetPort: 9443\n  selector:\n    control-plane: controller-manager\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: ` + projectName + `-validating-webhook-configuration\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: ` + projectName + `-webhook-service\n      namespace: ` + projectName + `-system\n      path: /validate\n  name: validate.example.com\n  sideEffects: None\n`\n}\n\nfunc createKustomizeWithWebhooksAndCertManager(projectName string) string {\n\treturn createKustomizeWithWebhooks(projectName) + `---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + projectName + `-selfsigned-issuer\n  namespace: ` + projectName + `-system\nspec:\n  selfSigned: {}\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + projectName + `-serving-cert\n  namespace: ` + projectName + `-system\nspec:\n  dnsNames:\n  - ` + projectName + `-webhook-service.` + projectName + `-system.svc\n  - ` + projectName + `-webhook-service.` + projectName + `-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: ` + projectName + `-selfsigned-issuer\n  secretName: webhook-server-cert\n`\n}\n\nfunc createKustomizeWithCustomPrefix(prefix, projectName string) string {\n\treturn `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + prefix + `-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n    control-plane: controller-manager\n  name: ` + prefix + `-controller-manager\n  namespace: ` + prefix + `-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: ` + projectName + `\n  name: ` + prefix + `-controller-manager-metrics-service\n  namespace: ` + prefix + `-system\nspec:\n  ports:\n  - port: 8443\n    targetPort: 8443\n`\n}\n\nfunc createKustomizeWithFullDeploymentConfig(projectName string) string {\n\treturn `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: ` + projectName + `-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ` + projectName + `-controller-manager\n  namespace: ` + projectName + `-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: myrepo/controller:v1.2.3\n        imagePullPolicy: IfNotPresent\n        args:\n        - --leader-elect\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        env:\n        - name: TEST_ENV\n          value: \"test-value\"\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n`\n}\n\nfunc setupKustomizeFile(filePath, content string) error {\n\tif err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(filePath, []byte(content), 0o644)\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/chart_never_overwrite_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"Chart.yaml Never Overwrite Test\", func() {\n\tvar (\n\t\tfs            machinery.Filesystem\n\t\ttmpDir        string\n\t\tmanifestsFile string\n\t\toutputDir     string\n\t\tprojectConfig config.Config\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-chart-never-overwrite-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\n\t\tprojectConfig = cfgv3.New()\n\t\tprojectConfig.SetProjectName(\"test-project\")\n\t\tprojectConfig.SetDomain(\"example.io\")\n\n\t\tmanifestsFile = filepath.Join(tmpDir, \"dist\", \"install.yaml\")\n\t\toutputDir = \"dist\"\n\n\t\t// Create minimal kustomize output\n\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-project-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  template:\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\n\t\terr = os.MkdirAll(filepath.Dir(manifestsFile), 0o755)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\terr = os.WriteFile(manifestsFile, []byte(kustomizeYAML), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tIt(\"should NEVER overwrite Chart.yaml even with --force=true\", func() {\n\t\tscaffolder := &editKustomizeScaffolder{\n\t\t\tconfig:        projectConfig,\n\t\t\tfs:            fs,\n\t\t\tforce:         false,\n\t\t\tmanifestsFile: manifestsFile,\n\t\t\toutputDir:     outputDir,\n\t\t}\n\n\t\t// First scaffold\n\t\terr := scaffolder.Scaffold()\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\", \"Chart.yaml\")\n\n\t\t// Read initial Chart.yaml\n\t\tinitialContent, err := os.ReadFile(chartPath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(initialContent)).To(ContainSubstring(\"name: test-project\"))\n\t\tExpect(string(initialContent)).To(ContainSubstring(\"version: 0.1.0\"))\n\n\t\t// Customize Chart.yaml with user version\n\t\tcustomChartYAML := `apiVersion: v2\nname: test-project\ndescription: My custom description\ntype: application\nversion: 1.2.3\nappVersion: \"1.2.3\"\nicon: \"https://mycompany.com/icon.png\"\nmaintainers:\n  - name: John Doe\n    email: john@example.com\n`\n\t\terr = os.WriteFile(chartPath, []byte(customChartYAML), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Scaffold again WITHOUT force\n\t\terr = scaffolder.Scaffold()\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Verify Chart.yaml was NOT overwritten\n\t\tcontent, err := os.ReadFile(chartPath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(Equal(customChartYAML), \"Chart.yaml should not be overwritten without --force\")\n\t\tExpect(string(content)).To(ContainSubstring(\"version: 1.2.3\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"John Doe\"))\n\n\t\t// Scaffold again WITH force=true\n\t\tscaffolder.force = true\n\t\terr = scaffolder.Scaffold()\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Verify Chart.yaml STILL was NOT overwritten (never overwritten even with force)\n\t\tcontent, err = os.ReadFile(chartPath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(Equal(customChartYAML), \"Chart.yaml should NEVER be overwritten, even with --force\")\n\t\tExpect(string(content)).To(ContainSubstring(\"version: 1.2.3\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"John Doe\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"My custom description\"))\n\t})\n\n\tIt(\"should preserve Chart.yaml on initial scaffold if file already exists\", func() {\n\t\t// Create Chart.yaml before any scaffolding\n\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\", \"Chart.yaml\")\n\t\terr := os.MkdirAll(filepath.Dir(chartPath), 0o755)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tpreexistingChart := `apiVersion: v2\nname: my-existing-chart\nversion: 99.99.99\n`\n\t\terr = os.WriteFile(chartPath, []byte(preexistingChart), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// First scaffold with force=true\n\t\tscaffolder := &editKustomizeScaffolder{\n\t\t\tconfig:        projectConfig,\n\t\t\tfs:            fs,\n\t\t\tforce:         true,\n\t\t\tmanifestsFile: manifestsFile,\n\t\t\toutputDir:     outputDir,\n\t\t}\n\n\t\terr = scaffolder.Scaffold()\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Verify Chart.yaml was preserved even on first scaffold with force\n\t\tcontent, err := os.ReadFile(chartPath)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tExpect(string(content)).To(Equal(preexistingChart), \"Chart.yaml should be preserved even on first scaffold with --force\")\n\t\tExpect(string(content)).To(ContainSubstring(\"my-existing-chart\"))\n\t\tExpect(string(content)).To(ContainSubstring(\"99.99.99\"))\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/edit_kustomize.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates\"\n\tcharttemplates \"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/github\"\n)\n\nconst (\n\tdefaultManifestsFile = \"dist/install.yaml\"\n)\n\nvar _ plugins.Scaffolder = &editKustomizeScaffolder{}\n\ntype editKustomizeScaffolder struct {\n\tconfig        config.Config\n\tfs            machinery.Filesystem\n\tforce         bool\n\tmanifestsFile string\n\toutputDir     string\n}\n\n// NewKustomizeHelmScaffolder returns a new Scaffolder for HelmPlugin using kustomize output\nfunc NewKustomizeHelmScaffolder(cfg config.Config, force bool, manifestsFile, outputDir string) plugins.Scaffolder {\n\treturn &editKustomizeScaffolder{\n\t\tconfig:        cfg,\n\t\tforce:         force,\n\t\tmanifestsFile: manifestsFile,\n\t\toutputDir:     outputDir,\n\t}\n}\n\n// InjectFS implements cmdutil.Scaffolder\nfunc (s *editKustomizeScaffolder) InjectFS(fs machinery.Filesystem) {\n\ts.fs = fs\n}\n\n// Scaffold generates the complete Helm chart from kustomize output\nfunc (s *editKustomizeScaffolder) Scaffold() error {\n\tslog.Info(\"Generating Helm Chart from kustomize output\")\n\n\t// Ensure chart directory structure exists\n\tif err := s.ensureChartDirectoryExists(); err != nil {\n\t\treturn fmt.Errorf(\"failed to create chart directory: %w\", err)\n\t}\n\n\t// Generate fresh kustomize output if using default file\n\tif s.manifestsFile == defaultManifestsFile {\n\t\tif err := s.generateKustomizeOutput(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to generate kustomize output: %w\", err)\n\t\t}\n\t}\n\n\t// Parse the kustomize output into organized resource groups\n\tparser := kustomize.NewParser(s.manifestsFile)\n\tresources, err := parser.Parse()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse kustomize output from %s: %w\", s.manifestsFile, err)\n\t}\n\n\t// Warn if Custom Resource instances were found and will be ignored\n\tif len(resources.CustomResources) > 0 {\n\t\tslog.Warn(\n\t\t\t\"Custom Resource instances found. They will be ignored and not included in the Helm chart\",\n\t\t\t\"count\", len(resources.CustomResources),\n\t\t\t\"note\", \"CRs are environment-specific and should be created manually after chart installation\",\n\t\t)\n\t\tfor _, cr := range resources.CustomResources {\n\t\t\tslog.Warn(\n\t\t\t\t\"Ignoring Custom Resource instance\",\n\t\t\t\t\"kind\", cr.GetKind(),\n\t\t\t\t\"apiVersion\", cr.GetAPIVersion(),\n\t\t\t\t\"name\", cr.GetName(),\n\t\t\t)\n\t\t}\n\t}\n\n\t// Analyze resources to determine chart features\n\thasWebhooks := len(resources.WebhookConfigurations) > 0 || len(resources.Certificates) > 0\n\t// Prometheus is enabled when ServiceMonitor resources exist (../prometheus enabled)\n\thasPrometheus := len(resources.ServiceMonitors) > 0\n\t// Metrics are enabled either when ServiceMonitor exists or when a metrics service is present\n\thasMetrics := hasPrometheus\n\tif !hasMetrics {\n\t\tfor _, svc := range resources.Services {\n\t\t\tif strings.Contains(svc.GetName(), \"metrics\") {\n\t\t\t\thasMetrics = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// When Prometheus is enabled via kustomize, ensure any previously-generated\n\t// generic ServiceMonitor file is removed to avoid duplicates in the chart.\n\tif hasPrometheus {\n\t\tstaleSM := filepath.Join(s.outputDir, \"chart\", \"templates\", \"monitoring\", \"servicemonitor.yaml\")\n\t\tif rmErr := s.fs.FS.Remove(staleSM); rmErr != nil && !os.IsNotExist(rmErr) {\n\t\t\t// Not fatal; log and continue\n\t\t\tslog.Warn(\"failed to remove stale generic ServiceMonitor\", \"path\", staleSM, \"error\", rmErr)\n\t\t}\n\t}\n\tnamePrefix := resources.EstimatePrefix(s.config.GetProjectName())\n\tchartName := s.config.GetProjectName()\n\tchartConverter := kustomize.NewChartConverter(resources, namePrefix, chartName, s.outputDir)\n\tdeploymentConfig := chartConverter.ExtractDeploymentConfig()\n\n\t// Create scaffold for standard Helm chart files (uses machinery defaults 0755/0644).\n\tscaffold := machinery.NewScaffold(s.fs, machinery.WithConfig(s.config))\n\n\t// Define the standard Helm chart files to generate\n\tchartFiles := []machinery.Builder{\n\t\t&github.HelmChartCI{Force: s.force},\n\t\t&templates.HelmChart{OutputDir: s.outputDir, Force: s.force},\n\t\t&templates.HelmValuesBasic{\n\t\t\t// values.yaml with dynamic config\n\t\t\tHasWebhooks:      hasWebhooks,\n\t\t\tHasMetrics:       hasMetrics,\n\t\t\tDeploymentConfig: deploymentConfig,\n\t\t\tOutputDir:        s.outputDir,\n\t\t\tForce:            s.force,\n\t\t},\n\t\t&templates.HelmIgnore{OutputDir: s.outputDir, Force: s.force},\n\t\t&charttemplates.HelmHelpers{OutputDir: s.outputDir, Force: s.force},\n\t\t&charttemplates.Notes{\n\t\t\tOutputDir: s.outputDir,\n\t\t\tForce:     s.force,\n\t\t},\n\t}\n\n\t// Only scaffold the generic ServiceMonitor when the project does NOT already\n\t// provide one via kustomize (../prometheus). This avoids duplicate objects\n\t// with the same name within the Helm chart.\n\tif !hasPrometheus {\n\t\t// Find the metrics service name from parsed resources\n\t\tmetricsServiceName := namePrefix + \"-controller-manager-metrics-service\"\n\t\tfor _, svc := range resources.Services {\n\t\t\tif strings.Contains(svc.GetName(), \"metrics-service\") {\n\t\t\t\tmetricsServiceName = svc.GetName()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tchartFiles = append(chartFiles, &charttemplates.ServiceMonitor{\n\t\t\tOutputDir:   s.outputDir,\n\t\t\tServiceName: metricsServiceName,\n\t\t\tForce:       s.force,\n\t\t})\n\t}\n\n\t// Generate template files from kustomize output\n\tif writeErr := chartConverter.WriteChartFiles(s.fs); writeErr != nil {\n\t\treturn fmt.Errorf(\"failed to write chart template files: %w\", writeErr)\n\t}\n\n\t// Generate standard Helm chart files\n\tif err = scaffold.Execute(chartFiles...); err != nil {\n\t\treturn fmt.Errorf(\"failed to generate Helm chart files: %w\", err)\n\t}\n\n\tslog.Info(\"Helm Chart generation completed successfully\")\n\treturn nil\n}\n\n// generateKustomizeOutput runs make build-installer to generate the manifests file\nfunc (s *editKustomizeScaffolder) generateKustomizeOutput() error {\n\tslog.Info(\"Generating kustomize output with make build-installer\")\n\n\t// Check if Makefile exists\n\tif _, err := os.Stat(\"Makefile\"); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"makefile not found in current directory\")\n\t}\n\n\t// Run make build-installer\n\tcmd := exec.Command(\"make\", \"build-installer\")\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run make build-installer: %w\", err)\n\t}\n\n\t// Verify that the manifests file was created\n\tif _, err := os.Stat(defaultManifestsFile); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"%s was not generated by make build-installer\", defaultManifestsFile)\n\t}\n\n\treturn nil\n}\n\n// ensureChartDirectoryExists creates the chart directory structure if it doesn't exist\nfunc (s *editKustomizeScaffolder) ensureChartDirectoryExists() error {\n\tdirs := []string{\n\t\tfilepath.Join(s.outputDir, \"chart\"),\n\t\tfilepath.Join(s.outputDir, \"chart\", \"templates\"),\n\t}\n\n\tfor _, dir := range dirs {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create directory %s: %w\", dir, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/extras_integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize\"\n)\n\nvar _ = Describe(\"Extras Directory Integration Test\", func() {\n\tvar (\n\t\tfs     machinery.Filesystem\n\t\ttmpDir string\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-extras-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t// Change to tmpDir so relative paths work correctly\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tContext(\"when converting Kustomize output with extra resources\", func() {\n\t\tIt(\"should place ConfigMap in extras directory with proper labels\", func() {\n\t\t\t// Create a simulated kustomize output with standard resources and a ConfigMap\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: custom-config\n  namespace: test-project-system\ndata:\n  key1: value1\n  key2: value2\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: custom-secret\n  namespace: test-project-system\ntype: Opaque\ndata:\n  password: c2VjcmV0Cg==\n`\n\n\t\t\tBy(\"writing kustomize output to a file\")\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"parsing the kustomize output\")\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).NotTo(BeNil())\n\n\t\t\tBy(\"verifying ConfigMap and Secret are in Other category\")\n\t\t\tExpect(resources.Other).To(HaveLen(2))\n\n\t\t\tBy(\"converting to Helm chart\")\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying extras directory was created\")\n\t\t\textrasDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"extras\")\n\t\t\texists, err := afero.Exists(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"extras directory should exist\")\n\n\t\t\tBy(\"verifying extras directory contains the ConfigMap and Secret\")\n\t\t\tfiles, err := afero.ReadDir(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(2), \"extras should contain ConfigMap and Secret\")\n\n\t\t\tvar configMapFile, secretFile string\n\t\t\tfor _, f := range files {\n\t\t\t\tif f.Name() == \"custom-config.yaml\" {\n\t\t\t\t\tconfigMapFile = f.Name()\n\t\t\t\t}\n\t\t\t\tif f.Name() == \"custom-secret.yaml\" {\n\t\t\t\t\tsecretFile = f.Name()\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(configMapFile).NotTo(BeEmpty(), \"ConfigMap file should exist\")\n\t\t\tExpect(secretFile).NotTo(BeEmpty(), \"Secret file should exist\")\n\n\t\t\tBy(\"verifying ConfigMap has proper Helm templating\")\n\t\t\tconfigMapPath := filepath.Join(extrasDir, configMapFile)\n\t\t\tcontent, err := afero.ReadFile(fs.FS, configMapPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tconfigMapContent := string(content)\n\n\t\t\t// Verify namespace templating\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"ConfigMap should have templated namespace\")\n\n\t\t\t// Note: Resource names without the project prefix are kept as-is\n\t\t\t// This allows users to have custom resource names that don't follow the project naming convention\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"name: custom-config\"),\n\t\t\t\t\"ConfigMap name should be preserved as-is when it doesn't match project prefix\")\n\n\t\t\t// Verify standard Helm labels\n\t\t\tExpect(configMapContent).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`),\n\t\t\t\t\"ConfigMap should have app.kubernetes.io/name label\")\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"app.kubernetes.io/instance: {{ .Release.Name }}\"),\n\t\t\t\t\"ConfigMap should have app.kubernetes.io/instance label\")\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"app.kubernetes.io/managed-by: {{ .Release.Service }}\"),\n\t\t\t\t\"ConfigMap should have app.kubernetes.io/managed-by label\")\n\t\t\tExpect(configMapContent).To(ContainSubstring(`helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}`),\n\t\t\t\t\"ConfigMap should have helm.sh/chart label\")\n\n\t\t\t// Verify data is preserved\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"key1: value1\"),\n\t\t\t\t\"ConfigMap data should be preserved\")\n\t\t\tExpect(configMapContent).To(ContainSubstring(\"key2: value2\"),\n\t\t\t\t\"ConfigMap data should be preserved\")\n\n\t\t\tBy(\"verifying Secret has proper Helm templating\")\n\t\t\tsecretPath := filepath.Join(extrasDir, secretFile)\n\t\t\tcontent, err = afero.ReadFile(fs.FS, secretPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tsecretContent := string(content)\n\n\t\t\t// Verify namespace templating\n\t\t\tExpect(secretContent).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"Secret should have templated namespace\")\n\n\t\t\t// Note: Resource names without the project prefix are kept as-is\n\t\t\tExpect(secretContent).To(ContainSubstring(\"name: custom-secret\"),\n\t\t\t\t\"Secret name should be preserved as-is when it doesn't match project prefix\")\n\n\t\t\t// Verify standard Helm labels\n\t\t\tExpect(secretContent).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`),\n\t\t\t\t\"Secret should have app.kubernetes.io/name label\")\n\t\t\tExpect(secretContent).To(ContainSubstring(\"app.kubernetes.io/managed-by: {{ .Release.Service }}\"),\n\t\t\t\t\"Secret should have app.kubernetes.io/managed-by label\")\n\n\t\t\t// Verify data is preserved\n\t\t\tExpect(secretContent).To(ContainSubstring(\"password: c2VjcmV0Cg==\"),\n\t\t\t\t\"Secret data should be preserved\")\n\t\t})\n\n\t\tIt(\"should place custom Service in extras directory\", func() {\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: custom-service\n  namespace: test-project-system\nspec:\n  ports:\n  - port: 8080\n    targetPort: 8080\n  selector:\n    app: custom\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying custom Service is in extras directory\")\n\t\t\textrasDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"extras\")\n\t\t\texists, err := afero.Exists(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\tfiles, err := afero.ReadDir(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1))\n\t\t\tExpect(files[0].Name()).To(Equal(\"custom-service.yaml\"))\n\n\t\t\tBy(\"verifying Service has proper Helm templating\")\n\t\t\tservicePath := filepath.Join(extrasDir, files[0].Name())\n\t\t\tcontent, err := afero.ReadFile(fs.FS, servicePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tserviceContent := string(content)\n\n\t\t\tExpect(serviceContent).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t\tExpect(serviceContent).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`))\n\t\t})\n\n\t\tIt(\"should not place webhook or metrics services in extras\", func() {\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-webhook-service\n  namespace: test-project-system\nspec:\n  ports:\n  - port: 443\n    targetPort: 9443\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-controller-manager-metrics-service\n  namespace: test-project-system\nspec:\n  ports:\n  - port: 8443\n    targetPort: 8443\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying extras directory was NOT created\")\n\t\t\textrasDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"extras\")\n\t\t\texists, err := afero.Exists(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeFalse(), \"extras directory should not exist for webhook/metrics services\")\n\n\t\t\tBy(\"verifying webhook directory was created\")\n\t\t\twebhookDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"webhook\")\n\t\t\texists, err = afero.Exists(fs.FS, webhookDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\tBy(\"verifying metrics directory was created\")\n\t\t\tmetricsDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"metrics\")\n\t\t\texists, err = afero.Exists(fs.FS, metricsDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\t\t})\n\t})\n\n\tContext(\"RBAC resource placement\", func() {\n\t\tIt(\"should ensure all RBAC resources go to rbac directory, never to extras\", func() {\n\t\t\t// Critical test: RBAC resources must NEVER end up in extras directory\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role\nrules:\n- apiGroups: [\"*\"]\n  resources: [\"*\"]\n  verbs: [\"*\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: test-project-system\nrules:\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"*\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-leader-election-rolebinding\n  namespace: test-project-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: custom-config\n  namespace: test-project-system\ndata:\n  key: value\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify parser correctly categorized RBAC resources\n\t\t\tExpect(resources.ServiceAccount).NotTo(BeNil(), \"ServiceAccount should be parsed\")\n\t\t\tExpect(resources.ClusterRoles).To(HaveLen(1), \"should have 1 ClusterRole\")\n\t\t\tExpect(resources.Roles).To(HaveLen(1), \"should have 1 Role\")\n\t\t\tExpect(resources.ClusterRoleBindings).To(HaveLen(1), \"should have 1 ClusterRoleBinding\")\n\t\t\tExpect(resources.RoleBindings).To(HaveLen(1), \"should have 1 RoleBinding\")\n\t\t\tExpect(resources.Other).To(HaveLen(1), \"ConfigMap should be in Other\")\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying rbac directory exists and contains all RBAC resources\")\n\t\t\trbacDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"rbac\")\n\t\t\texists, err := afero.Exists(fs.FS, rbacDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"rbac directory must exist\")\n\n\t\t\trbacFiles, err := afero.ReadDir(fs.FS, rbacDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Should have: 1 ServiceAccount + 1 ClusterRole + 1 Role + 1 ClusterRoleBinding + 1 RoleBinding = 5 files\n\t\t\tExpect(rbacFiles).To(HaveLen(5), \"rbac directory should have exactly 5 RBAC files\")\n\n\t\t\tBy(\"verifying NO RBAC resources in extras directory\")\n\t\t\textrasDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"extras\")\n\t\t\texists, err = afero.Exists(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"extras directory should exist for ConfigMap\")\n\n\t\t\textrasFiles, err := afero.ReadDir(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(extrasFiles).To(HaveLen(1), \"extras should only have ConfigMap, not RBAC\")\n\n\t\t\t// Verify the extras file is the ConfigMap, not any RBAC resource\n\t\t\tconfigMapFound := false\n\t\t\tfor _, f := range extrasFiles {\n\t\t\t\tif strings.Contains(f.Name(), \"custom-config\") {\n\t\t\t\t\tconfigMapFound = true\n\t\t\t\t}\n\t\t\t\t// Ensure no RBAC-related files\n\t\t\t\tExpect(f.Name()).NotTo(ContainSubstring(\"role\"), \"no Role files in extras\")\n\t\t\t\tExpect(f.Name()).NotTo(ContainSubstring(\"rolebinding\"), \"no RoleBinding files in extras\")\n\t\t\t\tExpect(f.Name()).NotTo(ContainSubstring(\"serviceaccount\"), \"no ServiceAccount files in extras\")\n\t\t\t}\n\t\t\tExpect(configMapFound).To(BeTrue(), \"ConfigMap should be in extras\")\n\t\t})\n\t})\n\n\tContext(\"when converting namespace-scoped RBAC resources\", func() {\n\t\tIt(\"should convert namespace-scoped Roles with explicit namespaces to Helm templates\", func() {\n\t\t\t// This test validates the scenario from issue where namespace-scoped Roles\n\t\t\t// (used for cross-namespace permissions, leader election, etc.) must be\n\t\t\t// included in the generated Helm chart, not just the ClusterRole.\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups: [\"example.com\"]\n  resources: [\"myresources\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-manager-role\n  namespace: infrastructure\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"deployments\"]\n  verbs: [\"get\", \"list\", \"patch\", \"update\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-events-role\n  namespace: production\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\", \"update\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  namespace: infrastructure\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-leader-election-rolebinding\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-events-rolebinding\n  namespace: production\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-events-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\n\t\t\tBy(\"writing kustomize output to a file\")\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"parsing the kustomize output\")\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).NotTo(BeNil())\n\n\t\t\tBy(\"verifying parser correctly categorized all RBAC resources\")\n\t\t\tExpect(resources.ClusterRoles).To(HaveLen(1), \"should have 1 ClusterRole\")\n\t\t\tExpect(resources.Roles).To(HaveLen(3), \"should have 3 namespace-scoped Roles\")\n\t\t\tExpect(resources.ClusterRoleBindings).To(HaveLen(1), \"should have 1 ClusterRoleBinding\")\n\t\t\tExpect(resources.RoleBindings).To(HaveLen(3), \"should have 3 RoleBindings\")\n\n\t\t\tBy(\"converting to Helm chart\")\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying rbac directory was created\")\n\t\t\trbacDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"rbac\")\n\t\t\texists, err := afero.Exists(fs.FS, rbacDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"rbac directory should exist\")\n\n\t\t\tBy(\"verifying all RBAC files are present\")\n\t\t\trbacFiles, err := afero.ReadDir(fs.FS, rbacDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Should have: 1 ServiceAccount + 1 ClusterRole + 1 ClusterRoleBinding + 3 Roles + 3 RoleBindings = 9 files\n\t\t\tExpect(rbacFiles).To(HaveLen(9), \"should have 9 RBAC files total\")\n\n\t\t\tBy(\"verifying ClusterRole file exists\")\n\t\t\t// ClusterRole has no namespace, so filename is just the name (with project prefix removed)\n\t\t\tclusterRolePath := filepath.Join(rbacDir, \"manager-role.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, clusterRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"ClusterRole file should exist\")\n\n\t\t\tBy(\"verifying infrastructure Role file exists\")\n\t\t\t// Role has namespace, so filename includes namespace suffix: name-namespace.yaml\n\t\t\tinfrastructureRolePath := filepath.Join(rbacDir, \"manager-role-infrastructure.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, infrastructureRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"infrastructure Role file should exist\")\n\n\t\t\tBy(\"verifying project namespace Role file exists\")\n\t\t\t// Role in project namespace (test-project-system) should NOT have namespace suffix\n\t\t\tprojectRolePath := filepath.Join(rbacDir, \"leader-election-role.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, projectRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"project namespace Role file should exist without suffix\")\n\n\t\t\tBy(\"verifying production Role file exists\")\n\t\t\t// Role in cross-namespace should have namespace suffix\n\t\t\tproductionRolePath := filepath.Join(rbacDir, \"events-role-production.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, productionRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"production Role file should exist with suffix\")\n\n\t\t\tBy(\"verifying infrastructure RoleBinding file exists\")\n\t\t\tinfrastructureBindingPath := filepath.Join(rbacDir, \"manager-rolebinding-infrastructure.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, infrastructureBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"infrastructure RoleBinding file should exist\")\n\n\t\t\tBy(\"verifying project namespace RoleBinding file exists\")\n\t\t\t// RoleBinding in project namespace should NOT have namespace suffix\n\t\t\tprojectBindingPath := filepath.Join(rbacDir, \"leader-election-rolebinding.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, projectBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"project namespace RoleBinding file should exist without suffix\")\n\n\t\t\tBy(\"verifying production RoleBinding file exists\")\n\t\t\t// RoleBinding in cross-namespace should have namespace suffix\n\t\t\tproductionBindingPath := filepath.Join(rbacDir, \"events-rolebinding-production.yaml\")\n\t\t\texists, err = afero.Exists(fs.FS, productionBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"production RoleBinding file should exist with suffix\")\n\n\t\t\tBy(\"verifying infrastructure Role has proper Helm templating\")\n\t\t\troleContent, err := afero.ReadFile(fs.FS, infrastructureRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\troleContentStr := string(roleContent)\n\n\t\t\t// Verify namespace is preserved (not templated to .Release.Namespace)\n\t\t\t// because it's an explicit cross-namespace permission\n\t\t\tExpect(roleContentStr).To(ContainSubstring(\"namespace: infrastructure\"),\n\t\t\t\t\"Role should preserve explicit namespace for cross-namespace permissions\")\n\n\t\t\t// Verify standard Helm labels\n\t\t\tExpect(roleContentStr).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`),\n\t\t\t\t\"Role should have templated app.kubernetes.io/name label\")\n\n\t\t\t// Verify name is templated\n\t\t\tExpect(roleContentStr).To(ContainSubstring(`name: {{ include \"test-project.resourceName\"`),\n\t\t\t\t\"Role name should be templated\")\n\n\t\t\t// Verify rules are preserved\n\t\t\tExpect(roleContentStr).To(ContainSubstring(\"apiGroups:\"),\n\t\t\t\t\"Role rules should be preserved\")\n\t\t\tExpect(roleContentStr).To(ContainSubstring(\"- apps\"),\n\t\t\t\t\"Role should have apps API group\")\n\t\t\tExpect(roleContentStr).To(ContainSubstring(\"- deployments\"),\n\t\t\t\t\"Role should have deployments resource\")\n\n\t\t\tBy(\"verifying project namespace Role has proper Helm templating\")\n\t\t\tprojRoleContent, err := afero.ReadFile(fs.FS, projectRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprojRoleContentStr := string(projRoleContent)\n\n\t\t\t// Verify namespace is templated to .Release.Namespace for project namespace\n\t\t\tExpect(projRoleContentStr).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"Role in project namespace should template namespace to .Release.Namespace\")\n\t\t\tExpect(projRoleContentStr).NotTo(ContainSubstring(\"namespace: test-project-system\"),\n\t\t\t\t\"Role should not have hardcoded project namespace\")\n\n\t\t\t// Verify leader election permissions\n\t\t\tExpect(projRoleContentStr).To(ContainSubstring(\"- coordination.k8s.io\"))\n\t\t\tExpect(projRoleContentStr).To(ContainSubstring(\"- leases\"))\n\n\t\t\tBy(\"verifying production Role has proper Helm templating\")\n\t\t\tprodRoleContent, err := afero.ReadFile(fs.FS, productionRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprodRoleContentStr := string(prodRoleContent)\n\n\t\t\t// Verify namespace is preserved for cross-namespace Role\n\t\t\tExpect(prodRoleContentStr).To(ContainSubstring(\"namespace: production\"),\n\t\t\t\t\"Role should preserve explicit namespace for cross-namespace permissions\")\n\n\t\t\t// Verify events permissions\n\t\t\tExpect(prodRoleContentStr).To(ContainSubstring(\"- events\"))\n\n\t\t\tBy(\"verifying infrastructure RoleBinding has proper Helm templating\")\n\t\t\tbindingContent, err := afero.ReadFile(fs.FS, infrastructureBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tbindingContentStr := string(bindingContent)\n\n\t\t\t// Verify namespace is preserved\n\t\t\tExpect(bindingContentStr).To(ContainSubstring(\"namespace: infrastructure\"),\n\t\t\t\t\"RoleBinding should preserve explicit namespace\")\n\n\t\t\t// Verify roleRef is templated\n\t\t\tExpect(bindingContentStr).To(ContainSubstring(`name: {{ include \"test-project.resourceName\"`),\n\t\t\t\t\"RoleBinding roleRef should be templated\")\n\n\t\t\t// Verify subjects namespace is templated (references the controller namespace)\n\t\t\tExpect(bindingContentStr).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"RoleBinding subject namespace should reference the release namespace\")\n\n\t\t\tBy(\"verifying project namespace RoleBinding has proper Helm templating\")\n\t\t\tprojBindingContent, err := afero.ReadFile(fs.FS, projectBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprojBindingContentStr := string(projBindingContent)\n\n\t\t\t// Verify namespace is templated for project namespace RoleBinding\n\t\t\tExpect(projBindingContentStr).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"RoleBinding metadata namespace should be templated for project namespace\")\n\n\t\t\t// Verify standard Helm labels\n\t\t\tExpect(projBindingContentStr).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`),\n\t\t\t\t\"RoleBinding should have templated labels\")\n\n\t\t\tBy(\"verifying production RoleBinding has proper Helm templating\")\n\t\t\tprodBindingContent, err := afero.ReadFile(fs.FS, productionBindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tprodBindingContentStr := string(prodBindingContent)\n\n\t\t\t// Verify namespace is preserved for cross-namespace RoleBinding\n\t\t\tExpect(prodBindingContentStr).To(ContainSubstring(\"namespace: production\"),\n\t\t\t\t\"RoleBinding should preserve explicit namespace for cross-namespace binding\")\n\n\t\t\t// Subject namespace should still be templated (references the controller namespace)\n\t\t\tExpect(prodBindingContentStr).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"RoleBinding subject namespace should be templated to Release.Namespace\")\n\n\t\t\tBy(\"verifying ClusterRole does not have namespace field\")\n\t\t\tclusterRoleContent, err := afero.ReadFile(fs.FS, clusterRolePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tclusterRoleContentStr := string(clusterRoleContent)\n\n\t\t\t// ClusterRole should not have namespace field\n\t\t\tExpect(clusterRoleContentStr).NotTo(ContainSubstring(\"namespace:\"),\n\t\t\t\t\"ClusterRole should not have namespace field\")\n\t\t})\n\n\t\tIt(\"should preserve ANY namespace field that differs from manager namespace\", func() {\n\t\t\t// This test validates that ANY namespace reference (metadata, subjects, etc.)\n\t\t\t// that is NOT the manager namespace gets preserved exactly as-is\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: external-sa\n  namespace: external-namespace\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-cross-ns-binding\n  namespace: infrastructure\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: some-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n- kind: ServiceAccount\n  name: external-sa\n  namespace: external-namespace\n- kind: ServiceAccount\n  name: another-sa\n  namespace: production\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      serviceAccountName: test-project-controller-manager\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying RoleBinding preserves all non-manager namespaces\")\n\t\t\trbacDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"rbac\")\n\t\t\tbindingPath := filepath.Join(rbacDir, \"cross-ns-binding-infrastructure.yaml\")\n\t\t\texists, err := afero.Exists(fs.FS, bindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"RoleBinding file should exist\")\n\n\t\t\tbindingContent, err := afero.ReadFile(fs.FS, bindingPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tbindingStr := string(bindingContent)\n\n\t\t\t// Metadata namespace should be preserved (cross-namespace)\n\t\t\tExpect(bindingStr).To(ContainSubstring(\"namespace: infrastructure\"),\n\t\t\t\t\"metadata namespace should be preserved\")\n\n\t\t\t// Manager namespace in subjects should be templated\n\t\t\tExpect(bindingStr).To(MatchRegexp(`kind: ServiceAccount\\s+name:.*\\s+namespace: \\{\\{ \\.Release\\.Namespace \\}\\}`),\n\t\t\t\t\"manager namespace in subject should be templated\")\n\n\t\t\t// External namespaces in subjects should be preserved\n\t\t\tExpect(bindingStr).To(ContainSubstring(\"namespace: external-namespace\"),\n\t\t\t\t\"external-namespace in subject should be preserved\")\n\t\t\tExpect(bindingStr).To(ContainSubstring(\"namespace: production\"),\n\t\t\t\t\"production namespace in subject should be preserved\")\n\n\t\t\t// Count namespace occurrences\n\t\t\tinfrastructureCount := strings.Count(bindingStr, \"namespace: infrastructure\")\n\t\t\texternalCount := strings.Count(bindingStr, \"namespace: external-namespace\")\n\t\t\tproductionCount := strings.Count(bindingStr, \"namespace: production\")\n\t\t\treleaseNsCount := strings.Count(bindingStr, \"namespace: {{ .Release.Namespace }}\")\n\n\t\t\tExpect(infrastructureCount).To(Equal(1), \"should have 1 infrastructure namespace (metadata)\")\n\t\t\tExpect(externalCount).To(Equal(1), \"should have 1 external-namespace (subject)\")\n\t\t\tExpect(productionCount).To(Equal(1), \"should have 1 production namespace (subject)\")\n\t\t\tExpect(releaseNsCount).To(BeNumerically(\">=\", 1), \"should have at least 1 templated namespace (manager subject)\")\n\n\t\t\tBy(\"verifying external ServiceAccount preserves its namespace\")\n\t\t\tsaFiles, err := afero.ReadDir(fs.FS, rbacDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tvar externalSAFound bool\n\t\t\tfor _, f := range saFiles {\n\t\t\t\tif strings.Contains(f.Name(), \"external-sa\") {\n\t\t\t\t\texternalSAFound = true\n\t\t\t\t\tsaPath := filepath.Join(rbacDir, f.Name())\n\t\t\t\t\tsaContent, err := afero.ReadFile(fs.FS, saPath)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tsaStr := string(saContent)\n\n\t\t\t\t\t// External SA namespace should be preserved\n\t\t\t\t\tExpect(saStr).To(ContainSubstring(\"namespace: external-namespace\"),\n\t\t\t\t\t\t\"external ServiceAccount namespace should be preserved\")\n\t\t\t\t\tExpect(saStr).NotTo(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\t\t\"external ServiceAccount should not use Release.Namespace\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(externalSAFound).To(BeTrue(), \"external ServiceAccount file should exist\")\n\t\t})\n\n\t\tIt(\"should escape existing Go template syntax in CRD samples\", func() {\n\t\t\t// Test a CRD with Go template syntax in default values.\n\t\t\t// Real-world example: gitops-promoter's ChangeTransferPolicy CRD has templates\n\t\t\t// in pullRequest.template fields that should be preserved as literal text.\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: changetransferpolicies.promoter.argoproj.io\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nspec:\n  group: promoter.argoproj.io\n  names:\n    kind: ChangeTransferPolicy\n    listKind: ChangeTransferPolicyList\n    plural: changetransferpolicies\n    singular: changetransferpolicy\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        description: ChangeTransferPolicy is the Schema for the changetransferpolicies API\n        properties:\n          spec:\n            properties:\n              activeBranch:\n                type: string\n              pullRequest:\n                properties:\n                  template:\n                    properties:\n                      description:\n                        default: \"Promoting {{ .ChangeTransferPolicy.Spec.ActiveBranch }}\"\n                        type: string\n                      title:\n                        default: \"Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}\"\n                        type: string\n                    type: object\n                type: object\n            type: object\n        type: object\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      serviceAccountName: test-project-controller-manager\n      containers:\n      - name: manager\n        image: controller:latest\n        args:\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying CRD file has escaped Go template syntax\")\n\t\t\tcrdDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"crd\")\n\t\t\tcrdPath := filepath.Join(crdDir, \"changetransferpolicies.promoter.argoproj.io.yaml\")\n\t\t\texists, err := afero.Exists(fs.FS, crdPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"CRD file should exist\")\n\n\t\t\tcrdContent, err := afero.ReadFile(fs.FS, crdPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcrdStr := string(crdContent)\n\n\t\t\t// Existing Go template syntax should be escaped to prevent Helm from parsing it\n\t\t\tExpect(crdStr).To(ContainSubstring(`{{ \"{{ .ChangeTransferPolicy.Spec.ActiveBranch }}\" }}`),\n\t\t\t\t\"existing template syntax should be escaped\")\n\t\t\tExpect(crdStr).To(ContainSubstring(`{{ \"{{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}\" }}`),\n\t\t\t\t\"template functions should be escaped\")\n\n\t\t\t// Verify we don't have unescaped template syntax that would break Helm rendering\n\t\t\t// We check that all ChangeTransferPolicy references are properly wrapped in escaped strings\n\t\t\t// Pattern checks for: default: \"...<text>{{ .ChangeTransferPolicy\" (not escaped)\n\t\t\t// The properly escaped version is: default: \"...{{ \"{{ .ChangeTransferPolicy...\" }}\"\n\t\t\tExpect(crdStr).NotTo(MatchRegexp(`default:\\s+\"[^{]*\\{\\{\\s*\\.ChangeTransferPolicy`),\n\t\t\t\t\"unescaped Go templates should not exist in default values\")\n\n\t\t\t// Helm templates we add should still work (not escaped)\n\t\t\tExpect(crdStr).To(ContainSubstring(\"{{- if .Values.crd.enable }}\"),\n\t\t\t\t\"Helm conditional should be present and NOT escaped\")\n\t\t\tExpect(crdStr).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"Helm namespace template should be present and NOT escaped\")\n\t\t\tExpect(crdStr).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`),\n\t\t\t\t\"Helm label template should be present and NOT escaped\")\n\t\t})\n\t})\n\n\tContext(\"Custom Resource instances\", func() {\n\t\tIt(\"should ignore Custom Resource instances and not include them in the chart\", func() {\n\t\t\t// This test validates that Custom Resources (CR instances, not CRDs) are\n\t\t\t// intentionally ignored and not included in the generated Helm chart.\n\t\t\t// CRs are environment-specific and should not be installed automatically.\n\t\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: test-project-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: cronjobs.batch.tutorial.kubebuilder.io\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nspec:\n  group: batch.tutorial.kubebuilder.io\n  names:\n    kind: CronJob\n    listKind: CronJobList\n    plural: cronjobs\n    singular: cronjob\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        description: CronJob is the Schema for the cronjobs API\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              schedule:\n                type: string\n---\napiVersion: batch.tutorial.kubebuilder.io/v1\nkind: CronJob\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project\n    app.kubernetes.io/managed-by: kustomize\n  name: cronjob-sample\nspec:\n  schedule: \"*/1 * * * *\"\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n  name: custom-config\n  namespace: test-project-system\ndata:\n  key1: value1\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      serviceAccountName: test-project-controller-manager\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\n\t\t\tkustomizeFile := filepath.Join(tmpDir, \"install.yaml\")\n\t\t\terr := os.WriteFile(kustomizeFile, []byte(kustomizeYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser := kustomize.NewParser(kustomizeFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying CRD and CR are correctly parsed\")\n\t\t\tExpect(resources.CustomResourceDefinitions).To(HaveLen(1), \"should have 1 CRD\")\n\t\t\tExpect(resources.CustomResources).To(HaveLen(1), \"should have 1 CR instance\")\n\t\t\tExpect(resources.Other).To(HaveLen(1), \"should have 1 other resource (ConfigMap)\")\n\n\t\t\tBy(\"verifying CR is a CronJob\")\n\t\t\tcr := resources.CustomResources[0]\n\t\t\tExpect(cr.GetKind()).To(Equal(\"CronJob\"))\n\t\t\tExpect(cr.GetAPIVersion()).To(Equal(\"batch.tutorial.kubebuilder.io/v1\"))\n\t\t\tExpect(cr.GetName()).To(Equal(\"cronjob-sample\"))\n\n\t\t\tconverter := kustomize.NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t\t\terr = converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying CR is NOT included in the chart (no samples directory)\")\n\t\t\tsamplesDir := filepath.Join(\"dist\", \"chart\", \"samples\")\n\t\t\texists, err := afero.Exists(fs.FS, samplesDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeFalse(), \"samples directory should NOT exist - CRs are ignored\")\n\n\t\t\tBy(\"verifying CR is NOT in extras directory\")\n\t\t\textrasDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"extras\")\n\t\t\texists, err = afero.Exists(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"extras directory should exist for ConfigMap\")\n\n\t\t\textrasFiles, err := afero.ReadDir(fs.FS, extrasDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(extrasFiles).To(HaveLen(1), \"extras should only have ConfigMap, not CR\")\n\n\t\t\tvar configMapFound, crFound bool\n\t\t\tfor _, f := range extrasFiles {\n\t\t\t\tif strings.Contains(f.Name(), \"custom-config\") {\n\t\t\t\t\tconfigMapFound = true\n\t\t\t\t}\n\t\t\t\tif strings.Contains(strings.ToLower(f.Name()), \"cronjob\") {\n\t\t\t\t\tcrFound = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tExpect(configMapFound).To(BeTrue(), \"ConfigMap should be in extras\")\n\t\t\tExpect(crFound).To(BeFalse(), \"CR should NOT be in extras\")\n\n\t\t\tBy(\"verifying CRD is in crd directory\")\n\t\t\tcrdDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"crd\")\n\t\t\texists, err = afero.Exists(fs.FS, crdDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue(), \"crd directory should exist\")\n\n\t\t\tcrdFiles, err := afero.ReadDir(fs.FS, crdDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(crdFiles).To(HaveLen(1), \"crd directory should have 1 CRD\")\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/force_integration_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/config\"\n\tcfgv3 \"sigs.k8s.io/kubebuilder/v4/pkg/config/v3\"\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"Force Flag Integration Test\", func() {\n\tvar (\n\t\tfs             machinery.Filesystem\n\t\ttmpDir         string\n\t\tmanifestsFile  string\n\t\toutputDir      string\n\t\tprojectConfig  config.Config\n\t\tscaffolderBase *editKustomizeScaffolder\n\t)\n\n\tBeforeEach(func() {\n\t\tvar err error\n\t\ttmpDir, err = os.MkdirTemp(\"\", \"helm-force-test-*\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\terr = os.Chdir(tmpDir)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tfs = machinery.Filesystem{\n\t\t\tFS: afero.NewBasePathFs(afero.NewOsFs(), tmpDir),\n\t\t}\n\n\t\t// Create PROJECT file\n\t\tprojectConfig = cfgv3.New()\n\t\tprojectConfig.SetProjectName(\"test-project\")\n\t\tprojectConfig.SetDomain(\"example.io\")\n\n\t\t// Setup directories - use absolute path for manifestsFile since parser uses os.Open\n\t\tmanifestsFile = filepath.Join(tmpDir, \"dist\", \"install.yaml\")\n\t\toutputDir = \"dist\"\n\n\t\t// Create minimal kustomize output file using real OS filesystem (parser uses os.Open)\n\t\tkustomizeYAML := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n        imagePullPolicy: IfNotPresent\n        command:\n        - /manager\n        args:\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        env:\n        - name: TEST_ENV\n          value: \"test-value\"\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n`\n\n\t\t// Use OS filesystem to write manifests file since Parser uses os.Open\n\t\terr = os.MkdirAll(filepath.Dir(manifestsFile), 0o755)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\terr = os.WriteFile(manifestsFile, []byte(kustomizeYAML), 0o644)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tscaffolderBase = &editKustomizeScaffolder{\n\t\t\tconfig:        projectConfig,\n\t\t\tfs:            fs,\n\t\t\tmanifestsFile: manifestsFile,\n\t\t\toutputDir:     outputDir,\n\t\t}\n\t})\n\n\tAfterEach(func() {\n\t\tif tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}\n\t})\n\n\tContext(\"when --force flag is NOT used\", func() {\n\t\tIt(\"should NOT overwrite existing Chart.yaml, values.yaml, .helmignore, _helpers.tpl, and test-chart.yml\", func() {\n\t\t\t// First generation with force=false\n\t\t\tscaffolderBase.force = false\n\t\t\terr := scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Define file paths (absolute paths for OS filesystem)\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\", \"Chart.yaml\")\n\t\t\tvaluesPath := filepath.Join(tmpDir, outputDir, \"chart\", \"values.yaml\")\n\t\t\thelmignorePath := filepath.Join(tmpDir, outputDir, \"chart\", \".helmignore\")\n\t\t\thelpersPath := filepath.Join(tmpDir, outputDir, \"chart\", \"templates\", \"_helpers.tpl\")\n\t\t\ttestChartPath := filepath.Join(tmpDir, \".github\", \"workflows\", \"test-chart.yml\")\n\n\t\t\t// Verify files exist\n\t\t\t_, err = os.ReadFile(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t_, err = os.ReadFile(valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t_, err = os.ReadFile(helmignorePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t_, err = os.ReadFile(helpersPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t_, err = os.ReadFile(testChartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Modify all protected files\n\t\t\tcustomChartContent := \"# CUSTOM CHART YAML\\nversion: 999.0.0\\n\"\n\t\t\tcustomValuesContent := \"# CUSTOM VALUES YAML\\ncustom: value\\n\"\n\t\t\tcustomHelmignoreContent := \"# CUSTOM HELMIGNORE\\n*.custom\\n\"\n\t\t\tcustomHelpersContent := \"# CUSTOM HELPERS TPL\\n{{/* custom helper */}}\\n\"\n\t\t\tcustomTestChartContent := \"# CUSTOM TEST CHART\\nname: Custom Workflow\\n\"\n\n\t\t\terr = os.WriteFile(chartPath, []byte(customChartContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(valuesPath, []byte(customValuesContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(helmignorePath, []byte(customHelmignoreContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(helpersPath, []byte(customHelpersContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(testChartPath, []byte(customTestChartContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Second generation with force=false\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify all protected files were NOT overwritten\n\t\t\tchartContent, err := os.ReadFile(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(chartContent)).To(Equal(customChartContent), \"Chart.yaml should not be overwritten without --force\")\n\n\t\t\tvaluesContent, err := os.ReadFile(valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(valuesContent)).To(Equal(customValuesContent), \"values.yaml should not be overwritten without --force\")\n\n\t\t\thelmignoreContent, err := os.ReadFile(helmignorePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(helmignoreContent)).To(Equal(customHelmignoreContent), \".helmignore should not be overwritten without --force\")\n\n\t\t\thelpersContent, err := os.ReadFile(helpersPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(helpersContent)).To(Equal(customHelpersContent), \"_helpers.tpl should not be overwritten without --force\")\n\n\t\t\ttestChartContent, err := os.ReadFile(testChartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(testChartContent)).To(Equal(customTestChartContent), \"test-chart.yml should not be overwritten without --force\")\n\t\t})\n\t})\n\n\tContext(\"when --force flag IS used\", func() {\n\t\tIt(\"should overwrite all files EXCEPT Chart.yaml (which is never overwritten)\", func() {\n\t\t\t// First generation with force=false\n\t\t\tscaffolderBase.force = false\n\t\t\terr := scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Define file paths (absolute paths for OS filesystem)\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\", \"Chart.yaml\")\n\t\t\tvaluesPath := filepath.Join(tmpDir, outputDir, \"chart\", \"values.yaml\")\n\t\t\thelmignorePath := filepath.Join(tmpDir, outputDir, \"chart\", \".helmignore\")\n\t\t\thelpersPath := filepath.Join(tmpDir, outputDir, \"chart\", \"templates\", \"_helpers.tpl\")\n\t\t\ttestChartPath := filepath.Join(tmpDir, \".github\", \"workflows\", \"test-chart.yml\")\n\n\t\t\t// Modify all protected files with custom content\n\t\t\tcustomChartContent := \"# CUSTOM CHART YAML\\nversion: 999.0.0\\n\"\n\t\t\tcustomValuesContent := \"# CUSTOM VALUES YAML\\ncustom: value\\n\"\n\t\t\tcustomHelmignoreContent := \"# CUSTOM HELMIGNORE\\n*.custom\\n\"\n\t\t\tcustomHelpersContent := \"# CUSTOM HELPERS TPL\\n{{/* custom helper */}}\\n\"\n\t\t\tcustomTestChartContent := \"# CUSTOM TEST CHART\\nname: Custom Workflow\\n\"\n\n\t\t\terr = os.WriteFile(chartPath, []byte(customChartContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(valuesPath, []byte(customValuesContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(helmignorePath, []byte(customHelmignoreContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(helpersPath, []byte(customHelpersContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(testChartPath, []byte(customTestChartContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Second generation with force=true\n\t\t\tscaffolderBase.force = true\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify Chart.yaml was NOT overwritten (never overwritten, even with --force)\n\t\t\tchartContent, err := os.ReadFile(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(chartContent)).To(Equal(customChartContent), \"Chart.yaml should NEVER be overwritten, even with --force\")\n\n\t\t\tvaluesContent, err := os.ReadFile(valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(valuesContent)).NotTo(Equal(customValuesContent), \"values.yaml should be overwritten with --force\")\n\t\t\tExpect(string(valuesContent)).To(ContainSubstring(\"manager:\"), \"values.yaml should contain manager section\")\n\n\t\t\thelmignoreContent, err := os.ReadFile(helmignorePath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(helmignoreContent)).NotTo(Equal(customHelmignoreContent), \".helmignore should be overwritten with --force\")\n\t\t\tExpect(string(helmignoreContent)).To(ContainSubstring(\".DS_Store\"), \".helmignore should contain default patterns\")\n\n\t\t\thelpersContent, err := os.ReadFile(helpersPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(helpersContent)).NotTo(Equal(customHelpersContent), \"_helpers.tpl should be overwritten with --force\")\n\t\t\tExpect(string(helpersContent)).To(ContainSubstring(\"test-project.name\"), \"_helpers.tpl should contain template helpers\")\n\n\t\t\ttestChartContent, err := os.ReadFile(testChartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(testChartContent)).NotTo(Equal(customTestChartContent), \"test-chart.yml should be overwritten with --force\")\n\t\t\tExpect(string(testChartContent)).To(ContainSubstring(\"Test Chart\"), \"test-chart.yml should contain workflow name\")\n\t\t})\n\n\t\tIt(\"should overwrite files on first run when force=true\", func() {\n\t\t\t// Create pre-existing custom files before any scaffold run (absolute paths)\n\t\t\tchartPath := filepath.Join(tmpDir, outputDir, \"chart\", \"Chart.yaml\")\n\t\t\tvaluesPath := filepath.Join(tmpDir, outputDir, \"chart\", \"values.yaml\")\n\n\t\t\terr := os.MkdirAll(filepath.Dir(chartPath), 0o755)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tcustomChartContent := \"# PRE-EXISTING CHART\\nversion: 0.0.1\\n\"\n\t\t\tcustomValuesContent := \"# PRE-EXISTING VALUES\\nold: data\\n\"\n\n\t\t\terr = os.WriteFile(chartPath, []byte(customChartContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\terr = os.WriteFile(valuesPath, []byte(customValuesContent), 0o644)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// First generation with force=true should overwrite\n\t\t\tscaffolderBase.force = true\n\t\t\terr = scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify Chart.yaml was NOT overwritten (never overwritten, even on first run with force)\n\t\t\tchartContent, err := os.ReadFile(chartPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(chartContent)).To(Equal(customChartContent), \"Chart.yaml should NEVER be overwritten, even on first run with --force\")\n\n\t\t\t// Verify values.yaml WAS overwritten\n\t\t\tvaluesContent, err := os.ReadFile(valuesPath)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(string(valuesContent)).NotTo(Equal(customValuesContent), \"values.yaml should be overwritten on first run with --force\")\n\t\t\tExpect(string(valuesContent)).To(ContainSubstring(\"manager:\"))\n\t\t})\n\t})\n\n\tContext(\"when template files are modified\", func() {\n\t\tIt(\"should verify template files exist in templates/ directory\", func() {\n\t\t\t// First generation\n\t\t\tscaffolderBase.force = false\n\t\t\terr := scaffolderBase.Scaffold()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify that template directory was created with files\n\t\t\ttemplatesDir := filepath.Join(tmpDir, outputDir, \"chart\", \"templates\")\n\t\t\tinfo, err := os.Stat(templatesDir)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Templates directory should be created\")\n\t\t\tExpect(info.IsDir()).To(BeTrue(), \"Templates should be a directory\")\n\n\t\t\t// At minimum, _helpers.tpl should exist\n\t\t\thelpersPath := filepath.Join(templatesDir, \"_helpers.tpl\")\n\t\t\t_, err = os.Stat(helpersPath)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"_helpers.tpl should exist in templates/\")\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// ChartConverter orchestrates the conversion of kustomize output to Helm chart templates\ntype ChartConverter struct {\n\tresources *ParsedResources\n\t// The actual namePrefix detected from kustomize resources\n\tdetectedPrefix string\n\t// The chart name used for template namespacing\n\tchartName string\n\toutputDir string\n\n\t// Components for conversion\n\torganizer *ResourceOrganizer\n\ttemplater *HelmTemplater\n\twriter    *ChartWriter\n}\n\n// NewChartConverter creates a new chart converter with all necessary components\nfunc NewChartConverter(resources *ParsedResources, detectedPrefix, chartName, outputDir string) *ChartConverter {\n\t// Extract manager namespace from Deployment, default to <prefix>-system\n\tmanagerNamespace := detectedPrefix + \"-system\"\n\tif resources.Deployment != nil {\n\t\tif ns := resources.Deployment.GetNamespace(); ns != \"\" {\n\t\t\tmanagerNamespace = ns\n\t\t}\n\t}\n\n\torganizer := NewResourceOrganizer(resources)\n\ttemplater := NewHelmTemplater(detectedPrefix, chartName, managerNamespace)\n\twriter := NewChartWriter(templater, outputDir, managerNamespace)\n\n\treturn &ChartConverter{\n\t\tresources:      resources,\n\t\tdetectedPrefix: detectedPrefix,\n\t\tchartName:      chartName,\n\t\toutputDir:      outputDir,\n\t\torganizer:      organizer,\n\t\ttemplater:      templater,\n\t\twriter:         writer,\n\t}\n}\n\n// WriteChartFiles converts all resources to Helm chart templates and writes them to the filesystem\nfunc (c *ChartConverter) WriteChartFiles(fs machinery.Filesystem) error {\n\t// Organize resources by their logical function\n\tresourceGroups := c.organizer.OrganizeByFunction()\n\n\t// Write each group to appropriate template files\n\tfor groupName, resources := range resourceGroups {\n\t\tif len(resources) > 0 {\n\t\t\t// De-duplicate exact resources by (apiVersion, kind, namespace, name)\n\t\t\tdeduped := dedupeResources(resources)\n\t\t\tif err := c.writer.WriteResourceGroup(fs, groupName, deduped); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to write %s resources: %w\", groupName, err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// dedupeResources removes exact duplicate resources by keying on\n// apiVersion, kind, namespace (optional), and name.\nfunc dedupeResources(resources []*unstructured.Unstructured) []*unstructured.Unstructured {\n\tseen := make(map[string]struct{})\n\tout := make([]*unstructured.Unstructured, 0, len(resources))\n\tfor _, r := range resources {\n\t\tif r == nil {\n\t\t\tcontinue\n\t\t}\n\t\tkey := r.GetAPIVersion() + \"|\" + r.GetKind() + \"|\" + r.GetNamespace() + \"|\" + r.GetName()\n\t\tif _, exists := seen[key]; exists {\n\t\t\tcontinue\n\t\t}\n\t\tseen[key] = struct{}{}\n\t\tout = append(out, r)\n\t}\n\treturn out\n}\n\n// ExtractDeploymentConfig extracts configuration values from the deployment for values.yaml\nfunc (c *ChartConverter) ExtractDeploymentConfig() map[string]any {\n\tif c.resources.Deployment == nil {\n\t\treturn make(map[string]any)\n\t}\n\n\tconfig := make(map[string]any)\n\tspecMap := extractDeploymentSpec(c.resources.Deployment)\n\tif specMap == nil {\n\t\treturn config\n\t}\n\n\textractPodSecurityContext(specMap, config)\n\textractImagePullSecrets(specMap, config)\n\textractPodNodeSelector(specMap, config)\n\textractPodTolerations(specMap, config)\n\textractPodAffinity(specMap, config)\n\n\tcontainer := firstManagerContainer(specMap)\n\tif container == nil {\n\t\treturn config\n\t}\n\n\textractContainerEnv(container, config)\n\textractContainerImage(container, config)\n\textractContainerArgs(container, config)\n\textractContainerPorts(container, config)\n\textractContainerResources(container, config)\n\textractContainerSecurityContext(container, config)\n\n\treturn config\n}\n\nfunc extractDeploymentSpec(deployment *unstructured.Unstructured) map[string]any {\n\tspec, found, err := unstructured.NestedFieldNoCopy(deployment.Object, \"spec\", \"template\", \"spec\")\n\tif !found || err != nil {\n\t\treturn nil\n\t}\n\n\tspecMap, ok := spec.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn specMap\n}\n\nfunc extractImagePullSecrets(specMap map[string]any, config map[string]any) {\n\timagePullSecrets, found, err := unstructured.NestedFieldNoCopy(specMap, \"imagePullSecrets\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\timagePullSecretsList, ok := imagePullSecrets.([]any)\n\tif !ok || len(imagePullSecretsList) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"imagePullSecrets\"] = imagePullSecretsList\n}\n\nfunc extractPodSecurityContext(specMap map[string]any, config map[string]any) {\n\tpodSecurityContext, found, err := unstructured.NestedFieldNoCopy(specMap, \"securityContext\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tpodSecMap, ok := podSecurityContext.(map[string]any)\n\tif !ok || len(podSecMap) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"podSecurityContext\"] = podSecurityContext\n}\n\nfunc extractPodNodeSelector(specMap map[string]any, config map[string]any) {\n\traw, found, err := unstructured.NestedFieldNoCopy(specMap, \"nodeSelector\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tresult, ok := raw.(map[string]any)\n\tif !ok || len(result) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"podNodeSelector\"] = result\n}\n\nfunc extractPodTolerations(specMap map[string]any, config map[string]any) {\n\traw, found, err := unstructured.NestedFieldNoCopy(specMap, \"tolerations\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tresult, ok := raw.([]any)\n\tif !ok || len(result) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"podTolerations\"] = result\n}\n\nfunc extractPodAffinity(specMap map[string]any, config map[string]any) {\n\traw, found, err := unstructured.NestedFieldNoCopy(specMap, \"affinity\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tresult, ok := raw.(map[string]any)\n\tif !ok || len(result) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"podAffinity\"] = result\n}\n\nfunc firstManagerContainer(specMap map[string]any) map[string]any {\n\tcontainers, found, err := unstructured.NestedFieldNoCopy(specMap, \"containers\")\n\tif !found || err != nil {\n\t\treturn nil\n\t}\n\n\tcontainersList, ok := containers.([]any)\n\tif !ok || len(containersList) == 0 {\n\t\treturn nil\n\t}\n\n\tfirstContainer, ok := containersList[0].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\treturn firstContainer\n}\n\nfunc extractContainerEnv(container map[string]any, config map[string]any) {\n\tenv, found, err := unstructured.NestedFieldNoCopy(container, \"env\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tenvList, ok := env.([]any)\n\tif !ok || len(envList) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"env\"] = envList\n}\n\nfunc extractContainerImage(container map[string]any, config map[string]any) {\n\timageValue, found, err := unstructured.NestedString(container, \"image\")\n\tif !found || err != nil || imageValue == \"\" {\n\t\treturn\n\t}\n\n\trepository := imageValue\n\ttag := \"latest\"\n\tlastColon := strings.LastIndex(imageValue, \":\")\n\tlastSlash := strings.LastIndex(imageValue, \"/\")\n\tif lastColon != -1 && lastColon > lastSlash {\n\t\trepository = imageValue[:lastColon]\n\t\tif lastColon+1 < len(imageValue) {\n\t\t\ttag = imageValue[lastColon+1:]\n\t\t}\n\t}\n\n\tpullPolicy, _, err := unstructured.NestedString(container, \"imagePullPolicy\")\n\tif err != nil || pullPolicy == \"\" {\n\t\tpullPolicy = \"IfNotPresent\"\n\t}\n\n\tconfig[\"image\"] = map[string]any{\n\t\t\"repository\": repository,\n\t\t\"tag\":        tag,\n\t\t\"pullPolicy\": pullPolicy,\n\t}\n}\n\nfunc extractContainerArgs(container map[string]any, config map[string]any) {\n\targs, found, err := unstructured.NestedFieldNoCopy(container, \"args\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\targsList, ok := args.([]any)\n\tif !ok || len(argsList) == 0 {\n\t\treturn\n\t}\n\n\tfilteredArgs := make([]any, 0, len(argsList))\n\tfor _, rawArg := range argsList {\n\t\tstrArg, ok := rawArg.(string)\n\t\tif !ok {\n\t\t\tfilteredArgs = append(filteredArgs, rawArg)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Extract port values from bind-address arguments and store them\n\t\t// These arguments should not be exposed under args because they will be\n\t\t// reconstructed from the port values in values.yaml\n\t\tif strings.Contains(strArg, \"--metrics-bind-address\") {\n\t\t\tif port := extractPortFromArg(strArg); port > 0 {\n\t\t\t\tif _, exists := config[\"metricsPort\"]; !exists {\n\t\t\t\t\tconfig[\"metricsPort\"] = port\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(strArg, \"--health-probe-bind-address\") {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(strArg, \"--webhook-cert-path\") ||\n\t\t\tstrings.Contains(strArg, \"--metrics-cert-path\") {\n\t\t\tcontinue\n\t\t}\n\t\tfilteredArgs = append(filteredArgs, strArg)\n\t}\n\n\tif len(filteredArgs) > 0 {\n\t\tconfig[\"args\"] = filteredArgs\n\t}\n}\n\n// extractPortFromArg extracts port number from arguments like \"--metrics-bind-address=:8443\"\nfunc extractPortFromArg(arg string) int {\n\t// Handle formats: --flag=:8443, --flag=0.0.0.0:8443, etc.\n\tparts := strings.Split(arg, \"=\")\n\tif len(parts) != 2 {\n\t\treturn 0\n\t}\n\n\tportPart := parts[1]\n\t// Remove leading : or host part\n\tif idx := strings.LastIndex(portPart, \":\"); idx != -1 {\n\t\tportPart = portPart[idx+1:]\n\t}\n\n\tport, err := strconv.Atoi(portPart)\n\tif err != nil || port <= 0 || port > 65535 {\n\t\treturn 0\n\t}\n\treturn port\n}\n\n// extractContainerPorts extracts port configurations from container ports\nfunc extractContainerPorts(container map[string]any, config map[string]any) {\n\t// Use NestedFieldNoCopy to avoid deep copy issues with int values\n\tportsField, found, err := unstructured.NestedFieldNoCopy(container, \"ports\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tports, ok := portsField.([]any)\n\tif !ok {\n\t\treturn\n\t}\n\n\tfor _, p := range ports {\n\t\tportMap, ok := p.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tname, _ := portMap[\"name\"].(string)\n\t\tvar containerPort int\n\n\t\t// Try int64 first (from YAML unmarshaling)\n\t\tif cp, ok := portMap[\"containerPort\"].(int64); ok {\n\t\t\tcontainerPort = int(cp)\n\t\t} else if cp, ok := portMap[\"containerPort\"].(int); ok {\n\t\t\tcontainerPort = cp\n\t\t} else {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Look for webhook-server port\n\t\tif name == \"webhook-server\" || strings.Contains(name, \"webhook\") {\n\t\t\tif _, exists := config[\"webhookPort\"]; !exists {\n\t\t\t\tconfig[\"webhookPort\"] = containerPort\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc extractContainerResources(container map[string]any, config map[string]any) {\n\tresources, found, err := unstructured.NestedFieldNoCopy(container, \"resources\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tresourcesMap, ok := resources.(map[string]any)\n\tif !ok || len(resourcesMap) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"resources\"] = resources\n}\n\nfunc extractContainerSecurityContext(container map[string]any, config map[string]any) {\n\tsecurityContext, found, err := unstructured.NestedFieldNoCopy(container, \"securityContext\")\n\tif !found || err != nil {\n\t\treturn\n\t}\n\n\tsecMap, ok := securityContext.(map[string]any)\n\tif !ok || len(secMap) == 0 {\n\t\treturn\n\t}\n\n\tconfig[\"securityContext\"] = securityContext\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_converter_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/spf13/afero\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"ChartConverter\", func() {\n\tvar (\n\t\tconverter *ChartConverter\n\t\tresources *ParsedResources\n\t\tfs        machinery.Filesystem\n\t)\n\n\tBeforeEach(func() {\n\t\t// Create test resources\n\t\tresources = &ParsedResources{}\n\n\t\t// Add a test deployment\n\t\tdeployment := &unstructured.Unstructured{}\n\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\tdeployment.SetKind(\"Deployment\")\n\t\tdeployment.SetName(\"test-controller\")\n\t\tdeployment.SetNamespace(\"test-system\")\n\n\t\t// Set deployment spec\n\t\terr := unstructured.SetNestedField(deployment.Object, int64(1), \"spec\", \"replicas\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tresources.Deployment = deployment\n\n\t\t// Create filesystem\n\t\tfs = machinery.Filesystem{FS: afero.NewMemMapFs()}\n\n\t\t// Create converter\n\t\tconverter = NewChartConverter(resources, \"test-project\", \"test-project\", \"dist\")\n\t})\n\n\tContext(\"NewChartConverter\", func() {\n\t\tIt(\"should create a converter with correct properties\", func() {\n\t\t\tExpect(converter.resources).To(Equal(resources))\n\t\t\tExpect(converter.detectedPrefix).To(Equal(\"test-project\"))\n\t\t\tExpect(converter.outputDir).To(Equal(\"dist\"))\n\t\t})\n\t})\n\n\tContext(\"WriteChartFiles\", func() {\n\t\tIt(\"should write chart files to filesystem\", func() {\n\t\t\t// Add some resources to test with\n\t\t\tserviceAccount := &unstructured.Unstructured{}\n\t\t\tserviceAccount.SetAPIVersion(\"v1\")\n\t\t\tserviceAccount.SetKind(\"ServiceAccount\")\n\t\t\tserviceAccount.SetName(\"test-sa\")\n\t\t\tserviceAccount.SetNamespace(\"test-system\")\n\t\t\tresources.ServiceAccount = serviceAccount\n\n\t\t\t// Add RBAC resources to test rbac directory creation\n\t\t\tclusterRole := &unstructured.Unstructured{}\n\t\t\tclusterRole.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tclusterRole.SetKind(\"ClusterRole\")\n\t\t\tclusterRole.SetName(\"test-role\")\n\t\t\tresources.ClusterRoles = []*unstructured.Unstructured{clusterRole}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\texists, err := afero.Exists(fs.FS, \"dist/chart/templates/manager\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\texists, err = afero.Exists(fs.FS, \"dist/chart/templates/rbac\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should deduplicate identical resources within a group\", func() {\n\t\t\t// Prepare two identical Services in the metrics group\n\t\t\tmetricsSvc1 := &unstructured.Unstructured{}\n\t\t\tmetricsSvc1.SetAPIVersion(\"v1\")\n\t\t\tmetricsSvc1.SetKind(\"Service\")\n\t\t\tmetricsSvc1.SetName(\"test-project-controller-manager-metrics-service\")\n\t\t\tmetricsSvc1.SetNamespace(\"test-system\")\n\n\t\t\tmetricsSvc2 := &unstructured.Unstructured{}\n\t\t\tmetricsSvc2.SetAPIVersion(\"v1\")\n\t\t\tmetricsSvc2.SetKind(\"Service\")\n\t\t\tmetricsSvc2.SetName(\"test-project-controller-manager-metrics-service\")\n\t\t\tmetricsSvc2.SetNamespace(\"test-system\")\n\n\t\t\t// Add both to resources; organizer will place them into the metrics group\n\t\t\tresources.Services = append(resources.Services, metricsSvc1, metricsSvc2)\n\n\t\t\t// Write chart files\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Expect only one file to be written for the metrics service after de-duplication\n\t\t\tmetricsDir := filepath.Join(\"dist\", \"chart\", \"templates\", \"metrics\")\n\t\t\tfiles, err := afero.ReadDir(fs.FS, metricsDir)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1), \"expected only one metrics service file after deduplication\")\n\t\t})\n\t})\n\n\tContext(\"ExtractDeploymentConfig\", func() {\n\t\tIt(\"should extract deployment configuration correctly\", func() {\n\t\t\t// Set up deployment with environment variables\n\t\t\tcontainers := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\":            \"manager\",\n\t\t\t\t\t\"image\":           \"controller:latest\",\n\t\t\t\t\t\"imagePullPolicy\": \"IfNotPresent\",\n\t\t\t\t\t\"args\": []any{\n\t\t\t\t\t\t\"--metrics-bind-address=:8443\",\n\t\t\t\t\t\t\"--leader-elect\",\n\t\t\t\t\t\t\"--custom-flag=value\",\n\t\t\t\t\t\t\"--health-probe-bind-address=:8081\",\n\t\t\t\t\t\t\"--webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\",\n\t\t\t\t\t},\n\t\t\t\t\t\"env\": []any{\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"name\":  \"TEST_ENV\",\n\t\t\t\t\t\t\t\"value\": \"test-value\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"resources\": map[string]any{\n\t\t\t\t\t\t\"limits\": map[string]any{\n\t\t\t\t\t\t\t\"cpu\":    \"100m\",\n\t\t\t\t\t\t\t\"memory\": \"128Mi\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\tcontainers,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"containers\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\n\t\t\tExpect(config).NotTo(BeNil())\n\t\t\tExpect(config).To(HaveKey(\"env\"))\n\t\t\tExpect(config).To(HaveKey(\"image\"))\n\t\t\tExpect(config).To(HaveKey(\"resources\"))\n\t\t\tExpect(config).To(HaveKey(\"args\"))\n\n\t\t\timageConfig, ok := config[\"image\"].(map[string]any)\n\t\t\tExpect(ok).To(BeTrue())\n\t\t\tExpect(imageConfig[\"repository\"]).To(Equal(\"controller\"))\n\t\t\tExpect(imageConfig[\"tag\"]).To(Equal(\"latest\"))\n\t\t\tExpect(imageConfig[\"pullPolicy\"]).To(Equal(\"IfNotPresent\"))\n\n\t\t\targs, ok := config[\"args\"].([]any)\n\t\t\tExpect(ok).To(BeTrue())\n\t\t\tExpect(args).To(ContainElement(\"--leader-elect\"))\n\t\t\tExpect(args).To(ContainElement(\"--custom-flag=value\"))\n\t\t\tExpect(args).NotTo(ContainElement(\"--metrics-bind-address=:8443\"))\n\t\t\tExpect(args).NotTo(ContainElement(\"--health-probe-bind-address=:8081\"))\n\t\t})\n\n\t\tIt(\"should extract port configurations from args\", func() {\n\t\t\t// Set up deployment with port-related args\n\t\t\tcontainers := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\":  \"manager\",\n\t\t\t\t\t\"image\": \"controller:latest\",\n\t\t\t\t\t\"args\": []any{\n\t\t\t\t\t\t\"--metrics-bind-address=:8443\",\n\t\t\t\t\t\t\"--health-probe-bind-address=:8081\",\n\t\t\t\t\t\t\"--leader-elect\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\tcontainers,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"containers\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\n\t\t\tExpect(config).To(HaveKey(\"metricsPort\"))\n\t\t\tExpect(config[\"metricsPort\"]).To(Equal(8443))\n\t\t\tExpect(config).NotTo(HaveKey(\"healthPort\"))\n\t\t})\n\n\t\tIt(\"should extract webhook port from container ports\", func() {\n\t\t\t// Set up deployment with webhook container port\n\t\t\tcontainers := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\":  \"manager\",\n\t\t\t\t\t\"image\": \"controller:latest\",\n\t\t\t\t\t\"ports\": []any{\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"containerPort\": int64(9443),\n\t\t\t\t\t\t\t\"name\":          \"webhook-server\",\n\t\t\t\t\t\t\t\"protocol\":      \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\tcontainers,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"containers\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\n\t\t\tExpect(config).To(HaveKey(\"webhookPort\"))\n\t\t\tExpect(config[\"webhookPort\"]).To(Equal(9443))\n\t\t})\n\n\t\tIt(\"should extract custom port values\", func() {\n\t\t\t// Set up deployment with custom ports\n\t\t\tcontainers := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\":  \"manager\",\n\t\t\t\t\t\"image\": \"controller:latest\",\n\t\t\t\t\t\"args\": []any{\n\t\t\t\t\t\t\"--metrics-bind-address=:9090\",\n\t\t\t\t\t\t\"--health-probe-bind-address=:9091\",\n\t\t\t\t\t},\n\t\t\t\t\t\"ports\": []any{\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"containerPort\": int64(9444),\n\t\t\t\t\t\t\t\"name\":          \"webhook-server\",\n\t\t\t\t\t\t\t\"protocol\":      \"TCP\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\tcontainers,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"containers\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\n\t\t\tExpect(config[\"metricsPort\"]).To(Equal(9090))\n\t\t\tExpect(config[\"healthPort\"]).To(BeNil())\n\t\t\tExpect(config[\"webhookPort\"]).To(Equal(9444))\n\t\t})\n\n\t\tIt(\"should extract imagePullSecrets\", func() {\n\t\t\t// Set up deployment with image pull secrets\n\t\t\tcontainers := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\":  \"manager\",\n\t\t\t\t\t\"image\": \"controller:latest\",\n\t\t\t\t},\n\t\t\t}\n\t\t\timagePullSecrets := []any{\n\t\t\t\tmap[string]any{\n\t\t\t\t\t\"name\": \"test-secret\",\n\t\t\t\t},\n\t\t\t}\n\t\t\t// Set the image pull secrets\n\t\t\terr := unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\timagePullSecrets,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"imagePullSecrets\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Set the containers\n\t\t\terr = unstructured.SetNestedSlice(\n\t\t\t\tresources.Deployment.Object,\n\t\t\t\tcontainers,\n\t\t\t\t\"spec\", \"template\", \"spec\", \"containers\",\n\t\t\t)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\t\t\tExpect(config).To(HaveKey(\"imagePullSecrets\"))\n\t\t\tExpect(config[\"imagePullSecrets\"]).To(Equal(imagePullSecrets))\n\t\t})\n\n\t\tIt(\"should handle deployment without containers\", func() {\n\t\t\tconfig := converter.ExtractDeploymentConfig()\n\t\t\tExpect(config).To(BeEmpty())\n\t\t})\n\t})\n\n\tContext(\"extractPortFromArg\", func() {\n\t\tIt(\"should extract port from :PORT format\", func() {\n\t\t\tport := extractPortFromArg(\"--metrics-bind-address=:8443\")\n\t\t\tExpect(port).To(Equal(8443))\n\t\t})\n\n\t\tIt(\"should extract port from 0.0.0.0:PORT format\", func() {\n\t\t\tport := extractPortFromArg(\"--metrics-bind-address=0.0.0.0:8443\")\n\t\t\tExpect(port).To(Equal(8443))\n\t\t})\n\n\t\tIt(\"should extract port from HOST:PORT format\", func() {\n\t\t\tport := extractPortFromArg(\"--health-probe-bind-address=localhost:8081\")\n\t\t\tExpect(port).To(Equal(8081))\n\t\t})\n\n\t\tIt(\"should return 0 for invalid formats\", func() {\n\t\t\tport := extractPortFromArg(\"--invalid-arg\")\n\t\t\tExpect(port).To(Equal(0))\n\n\t\t\tport = extractPortFromArg(\"--no-equals:8443\")\n\t\t\tExpect(port).To(Equal(0))\n\n\t\t\tport = extractPortFromArg(\"--port=invalid\")\n\t\t\tExpect(port).To(Equal(0))\n\t\t})\n\n\t\tIt(\"should return 0 for out-of-range ports\", func() {\n\t\t\tport := extractPortFromArg(\"--port=:0\")\n\t\t\tExpect(port).To(Equal(0))\n\n\t\t\tport = extractPortFromArg(\"--port=:99999\")\n\t\t\tExpect(port).To(Equal(0))\n\t\t})\n\t})\n\n\tContext(\"Extras Directory\", func() {\n\t\tIt(\"should place ConfigMap in extras directory\", func() {\n\t\t\t// Create a ConfigMap that doesn't fit standard categories\n\t\t\tconfigMap := &unstructured.Unstructured{}\n\t\t\tconfigMap.SetAPIVersion(\"v1\")\n\t\t\tconfigMap.SetKind(\"ConfigMap\")\n\t\t\tconfigMap.SetName(\"custom-config\")\n\t\t\tconfigMap.SetNamespace(\"test-system\")\n\t\t\tconfigMap.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"custom-config\",\n\t\t\t\t\"namespace\": \"test-system\",\n\t\t\t\t\"labels\": map[string]any{\n\t\t\t\t\t\"app.kubernetes.io/name\":       \"test-project\",\n\t\t\t\t\t\"app.kubernetes.io/managed-by\": \"kustomize\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tconfigMap.Object[\"data\"] = map[string]any{\n\t\t\t\t\"key\": \"value\",\n\t\t\t}\n\n\t\t\tresources.Other = []*unstructured.Unstructured{configMap}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify extras directory was created\n\t\t\texists, err := afero.Exists(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\t// Verify ConfigMap file was created\n\t\t\tfiles, err := afero.ReadDir(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1))\n\t\t\tExpect(files[0].Name()).To(ContainSubstring(\"custom-config\"))\n\n\t\t\t// Read the ConfigMap file and verify it has Helm templating\n\t\t\tcontent, err := afero.ReadFile(fs.FS, filepath.Join(\"dist/chart/templates/extras\", files[0].Name()))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\n\t\t\t// Verify Helm templates are applied\n\t\t\tExpect(contentStr).To(ContainSubstring(\"{{ .Release.Namespace }}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"app.kubernetes.io/name:\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"app.kubernetes.io/managed-by:\"))\n\t\t})\n\n\t\tIt(\"should place custom Service in extras directory\", func() {\n\t\t\t// Create a custom Service that is neither webhook nor metrics\n\t\t\tcustomService := &unstructured.Unstructured{}\n\t\t\tcustomService.SetAPIVersion(\"v1\")\n\t\t\tcustomService.SetKind(\"Service\")\n\t\t\tcustomService.SetName(\"custom-service\")\n\t\t\tcustomService.SetNamespace(\"test-project-system\")\n\t\t\tcustomService.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"custom-service\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t\t\"labels\": map[string]any{\n\t\t\t\t\t\"app.kubernetes.io/name\":       \"test-project\",\n\t\t\t\t\t\"app.kubernetes.io/managed-by\": \"kustomize\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tcustomService.Object[\"spec\"] = map[string]any{\n\t\t\t\t\"ports\": []any{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"port\":       8080,\n\t\t\t\t\t\t\"targetPort\": 8080,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tresources.Services = []*unstructured.Unstructured{customService}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify extras directory was created\n\t\t\texists, err := afero.Exists(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\t// Verify Service file was created in extras\n\t\t\tfiles, err := afero.ReadDir(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1))\n\t\t\tExpect(files[0].Name()).To(ContainSubstring(\"custom-service\"))\n\t\t})\n\n\t\tIt(\"should place Secret in extras directory\", func() {\n\t\t\t// Create a Secret\n\t\t\tsecret := &unstructured.Unstructured{}\n\t\t\tsecret.SetAPIVersion(\"v1\")\n\t\t\tsecret.SetKind(\"Secret\")\n\t\t\tsecret.SetName(\"custom-secret\")\n\t\t\tsecret.SetNamespace(\"test-system\")\n\t\t\tsecret.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"custom-secret\",\n\t\t\t\t\"namespace\": \"test-system\",\n\t\t\t\t\"labels\": map[string]any{\n\t\t\t\t\t\"app.kubernetes.io/name\":       \"test-project\",\n\t\t\t\t\t\"app.kubernetes.io/managed-by\": \"kustomize\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tsecret.Object[\"data\"] = map[string]any{\n\t\t\t\t\"password\": \"c2VjcmV0\",\n\t\t\t}\n\n\t\t\tresources.Other = []*unstructured.Unstructured{secret}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify extras directory was created\n\t\t\texists, err := afero.Exists(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\t// Verify Secret file was created\n\t\t\tfiles, err := afero.ReadDir(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1))\n\t\t\tExpect(files[0].Name()).To(ContainSubstring(\"custom-secret\"))\n\n\t\t\t// Read the Secret file and verify it has Helm templating\n\t\t\tcontent, err := afero.ReadFile(fs.FS, filepath.Join(\"dist/chart/templates/extras\", files[0].Name()))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\n\t\t\t// Verify Helm templates are applied\n\t\t\tExpect(contentStr).To(ContainSubstring(\"{{ .Release.Namespace }}\"))\n\t\t})\n\n\t\tIt(\"should handle multiple extras resources\", func() {\n\t\t\t// Create multiple extras resources\n\t\t\tconfigMap := &unstructured.Unstructured{}\n\t\t\tconfigMap.SetAPIVersion(\"v1\")\n\t\t\tconfigMap.SetKind(\"ConfigMap\")\n\t\t\tconfigMap.SetName(\"config1\")\n\t\t\tconfigMap.SetNamespace(\"test-project-system\")\n\t\t\tconfigMap.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"config1\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t}\n\n\t\t\tsecret := &unstructured.Unstructured{}\n\t\t\tsecret.SetAPIVersion(\"v1\")\n\t\t\tsecret.SetKind(\"Secret\")\n\t\t\tsecret.SetName(\"secret1\")\n\t\t\tsecret.SetNamespace(\"test-project-system\")\n\t\t\tsecret.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"secret1\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t}\n\n\t\t\tcustomService := &unstructured.Unstructured{}\n\t\t\tcustomService.SetAPIVersion(\"v1\")\n\t\t\tcustomService.SetKind(\"Service\")\n\t\t\tcustomService.SetName(\"custom-svc\")\n\t\t\tcustomService.SetNamespace(\"test-project-system\")\n\t\t\tcustomService.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"custom-svc\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t}\n\n\t\t\tresources.Other = []*unstructured.Unstructured{configMap, secret}\n\t\t\tresources.Services = []*unstructured.Unstructured{customService}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify all three files were created\n\t\t\tfiles, err := afero.ReadDir(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(3))\n\t\t})\n\n\t\tIt(\"should apply standard Helm labels to extras resources\", func() {\n\t\t\t// Create a ConfigMap\n\t\t\tconfigMap := &unstructured.Unstructured{}\n\t\t\tconfigMap.SetAPIVersion(\"v1\")\n\t\t\tconfigMap.SetKind(\"ConfigMap\")\n\t\t\tconfigMap.SetName(\"test-config\")\n\t\t\tconfigMap.SetNamespace(\"test-system\")\n\t\t\tconfigMap.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"test-config\",\n\t\t\t\t\"namespace\": \"test-system\",\n\t\t\t\t\"labels\": map[string]any{\n\t\t\t\t\t\"app.kubernetes.io/name\":       \"test-project\",\n\t\t\t\t\t\"app.kubernetes.io/managed-by\": \"kustomize\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tresources.Other = []*unstructured.Unstructured{configMap}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Read the ConfigMap file\n\t\t\tfiles, err := afero.ReadDir(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(files).To(HaveLen(1))\n\n\t\t\tcontent, err := afero.ReadFile(fs.FS, filepath.Join(\"dist/chart/templates/extras\", files[0].Name()))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tcontentStr := string(content)\n\n\t\t\t// Verify all standard Helm labels are present\n\t\t\tExpect(contentStr).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"app.kubernetes.io/instance: {{ .Release.Name }}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\"app.kubernetes.io/managed-by: {{ .Release.Service }}\"))\n\t\t\tExpect(contentStr).To(ContainSubstring(\n\t\t\t\t`helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}`))\n\t\t})\n\n\t\tIt(\"should not place webhook or metrics services in extras\", func() {\n\t\t\t// Create webhook service\n\t\t\twebhookService := &unstructured.Unstructured{}\n\t\t\twebhookService.SetAPIVersion(\"v1\")\n\t\t\twebhookService.SetKind(\"Service\")\n\t\t\twebhookService.SetName(\"test-project-webhook-service\")\n\t\t\twebhookService.SetNamespace(\"test-project-system\")\n\t\t\twebhookService.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"test-project-webhook-service\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t}\n\n\t\t\t// Create metrics service\n\t\t\tmetricsService := &unstructured.Unstructured{}\n\t\t\tmetricsService.SetAPIVersion(\"v1\")\n\t\t\tmetricsService.SetKind(\"Service\")\n\t\t\tmetricsService.SetName(\"test-project-controller-manager-metrics-service\")\n\t\t\tmetricsService.SetNamespace(\"test-project-system\")\n\t\t\tmetricsService.Object[\"metadata\"] = map[string]any{\n\t\t\t\t\"name\":      \"test-project-controller-manager-metrics-service\",\n\t\t\t\t\"namespace\": \"test-project-system\",\n\t\t\t}\n\n\t\t\tresources.Services = []*unstructured.Unstructured{webhookService, metricsService}\n\n\t\t\terr := converter.WriteChartFiles(fs)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Verify extras directory was not created (webhook/metrics go to their own dirs)\n\t\t\texists, err := afero.Exists(fs.FS, \"dist/chart/templates/extras\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeFalse())\n\n\t\t\t// Verify webhook directory was created\n\t\t\texists, err = afero.Exists(fs.FS, \"dist/chart/templates/webhook\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\n\t\t\t// Verify metrics directory was created\n\t\t\texists, err = afero.Exists(fs.FS, \"dist/chart/templates/metrics\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(exists).To(BeTrue())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/chart_writer.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/afero\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// ChartWriter handles writing Helm chart template files\ntype ChartWriter struct {\n\ttemplater        *HelmTemplater\n\toutputDir        string\n\tmanagerNamespace string\n}\n\n// NewChartWriter creates a new chart writer\nfunc NewChartWriter(templater *HelmTemplater, outputDir string, managerNamespace string) *ChartWriter {\n\treturn &ChartWriter{\n\t\ttemplater:        templater,\n\t\toutputDir:        outputDir,\n\t\tmanagerNamespace: managerNamespace,\n\t}\n}\n\n// WriteResourceGroup writes a group of resources to a Helm template file\nfunc (w *ChartWriter) WriteResourceGroup(\n\tfs machinery.Filesystem, groupName string,\n\tresources []*unstructured.Unstructured,\n) error {\n\t// Special handling for namespace - write as single file\n\tif groupName == \"namespace\" {\n\t\treturn w.writeNamespaceFile(fs, resources[0])\n\t}\n\n\t// For CRDs, certificates, and other resources that should be split, write individual files\n\tif w.shouldSplitFiles(groupName) {\n\t\treturn w.writeSplitFiles(fs, groupName, resources)\n\t}\n\n\t// For other groups, write as directory-based template files\n\treturn w.writeGroupDirectory(fs, groupName, resources)\n}\n\n// writeNamespaceFile writes the namespace as a single file in templates/\nfunc (w *ChartWriter) writeNamespaceFile(fs machinery.Filesystem, namespace *unstructured.Unstructured) error {\n\t// Apply Helm templating\n\tyamlContent := w.convertToYAML(namespace)\n\tyamlContent = w.templater.ApplyHelmSubstitutions(yamlContent, namespace)\n\n\t// Write to templates/namespace.yaml\n\tfilePath := filepath.Join(w.outputDir, \"chart\", \"templates\", \"namespace.yaml\")\n\treturn w.writeFileWithNewline(fs, filePath, yamlContent)\n}\n\n// writeGroupDirectory writes resources as files in a group-specific directory\nfunc (w *ChartWriter) writeGroupDirectory(\n\tfs machinery.Filesystem, groupName string,\n\tresources []*unstructured.Unstructured,\n) error {\n\tvar finalContent bytes.Buffer\n\n\t// Convert each resource to YAML and apply templating\n\tfor i, resource := range resources {\n\t\tif i > 0 {\n\t\t\tfinalContent.WriteString(\"---\\n\")\n\t\t}\n\n\t\tyamlContent := w.convertToYAML(resource)\n\t\tyamlContent = w.templater.ApplyHelmSubstitutions(yamlContent, resource)\n\t\tfinalContent.WriteString(yamlContent)\n\t}\n\n\t// Write to templates/{groupName}/{groupName}.yaml\n\tdirPath := filepath.Join(w.outputDir, \"chart\", \"templates\", groupName)\n\tfilePath := filepath.Join(dirPath, groupName+\".yaml\")\n\n\treturn w.writeFileWithNewline(fs, filePath, finalContent.String())\n}\n\n// convertToYAML converts an unstructured object to YAML string with 2-space indentation\nfunc (w *ChartWriter) convertToYAML(resource *unstructured.Unstructured) string {\n\tyamlBytes, err := yaml.Marshal(resource.Object)\n\tif err != nil {\n\t\treturn fmt.Sprintf(\"# Error converting to YAML: %v\\n\", err)\n\t}\n\treturn string(yamlBytes)\n}\n\n// shouldSplitFiles determines if resources in a group should be written as individual files\nfunc (w *ChartWriter) shouldSplitFiles(groupName string) bool {\n\treturn groupName == \"crd\" || groupName == \"cert-manager\" || groupName == \"webhook\" ||\n\t\tgroupName == \"prometheus\" || groupName == \"rbac\" || groupName == \"metrics\" ||\n\t\tgroupName == \"extras\"\n}\n\n// writeSplitFiles writes each resource in the group to its own file\nfunc (w *ChartWriter) writeSplitFiles(\n\tfs machinery.Filesystem, groupName string,\n\tresources []*unstructured.Unstructured,\n) error {\n\t// Create the group directory\n\tgroupDir := filepath.Join(w.outputDir, \"chart\", \"templates\", groupName)\n\tif err := fs.FS.MkdirAll(groupDir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"creating group directory %s: %w\", groupDir, err)\n\t}\n\n\t// Write each resource to its own file\n\tfor i, resource := range resources {\n\t\tfileName := w.generateFileName(resource, i)\n\t\tfilePath := filepath.Join(groupDir, fileName)\n\n\t\tyamlContent := w.convertToYAML(resource)\n\t\tyamlContent = w.templater.ApplyHelmSubstitutions(yamlContent, resource)\n\n\t\tif err := w.writeFileWithNewline(fs, filePath, yamlContent); err != nil {\n\t\t\treturn fmt.Errorf(\"writing resource file %s: %w\", filePath, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// generateFileName creates a unique filename for a resource based on its metadata\nfunc (w *ChartWriter) generateFileName(resource *unstructured.Unstructured, index int) string {\n\t// Try to use the resource name if available\n\tif name := resource.GetName(); name != \"\" {\n\t\t// Remove project prefix from the filename for cleaner file names\n\t\tprojectPrefix := w.templater.detectedPrefix + \"-\"\n\t\tfileName := name\n\t\tif after, ok := strings.CutPrefix(name, projectPrefix); ok {\n\t\t\tfileName = after\n\t\t}\n\n\t\t// Handle special cases where filename might be empty after prefix removal\n\t\tif fileName == \"\" {\n\t\t\tfileName = resource.GetKind()\n\t\t\tif fileName == \"\" {\n\t\t\t\tfileName = \"resource\"\n\t\t\t}\n\t\t}\n\n\t\t// For namespace-scoped RBAC (Role, RoleBinding), append namespace suffix\n\t\t// only for cross-namespace resources to prevent filename collisions.\n\t\t// Resources in the manager namespace don't need a suffix since they're unique.\n\t\tkind := resource.GetKind()\n\t\tnamespace := resource.GetNamespace()\n\t\tif namespace != \"\" && (kind == \"Role\" || kind == \"RoleBinding\") {\n\t\t\tif namespace != w.managerNamespace {\n\t\t\t\tfileName = fmt.Sprintf(\"%s-%s\", fileName, namespace)\n\t\t\t}\n\t\t}\n\n\t\t// Replace dots and other special characters with underscores for filename safety\n\t\tfileName = filepath.Base(fileName) // Remove any path separators\n\t\treturn fmt.Sprintf(\"%s.yaml\", fileName)\n\t}\n\n\t// Fall back to kind + index if no name\n\tkind := resource.GetKind()\n\tif kind == \"\" {\n\t\tkind = \"resource\"\n\t}\n\treturn fmt.Sprintf(\"%s-%d.yaml\", kind, index)\n}\n\n// writeFileWithNewline ensures the file ends with a newline\nfunc (w *ChartWriter) writeFileWithNewline(fs machinery.Filesystem, filePath, content string) error {\n\t// Ensure content ends with newline\n\tif content != \"\" && content[len(content)-1] != '\\n' {\n\t\tcontent += \"\\n\"\n\t}\n\n\t// Create directory if it doesn't exist\n\tdir := filepath.Dir(filePath)\n\tif err := fs.FS.MkdirAll(dir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"creating directory %s: %w\", dir, err)\n\t}\n\n\t// Use afero to write directly through the filesystem\n\tif err := afero.WriteFile(fs.FS, filePath, []byte(content), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"writing file %s: %w\", filePath, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nconst (\n\tkindNamespace          = \"Namespace\"\n\tkindCertificate        = \"Certificate\"\n\tkindService            = \"Service\"\n\tkindServiceAccount     = \"ServiceAccount\"\n\tkindRole               = \"Role\"\n\tkindClusterRole        = \"ClusterRole\"\n\tkindRoleBinding        = \"RoleBinding\"\n\tkindClusterRoleBinding = \"ClusterRoleBinding\"\n\tkindServiceMonitor     = \"ServiceMonitor\"\n\tkindIssuer             = \"Issuer\"\n\tkindValidatingWebhook  = \"ValidatingWebhookConfiguration\"\n\tkindMutatingWebhook    = \"MutatingWebhookConfiguration\"\n\tkindDeployment         = \"Deployment\"\n\tkindCRD                = \"CustomResourceDefinition\"\n\n\t// API versions\n\tapiVersionCertManager = \"cert-manager.io/v1\"\n\tapiVersionMonitoring  = \"monitoring.coreos.com/v1\"\n)\n\n// HelmTemplater handles converting YAML content to Helm templates\ntype HelmTemplater struct {\n\tdetectedPrefix   string\n\tchartName        string\n\tmanagerNamespace string\n}\n\n// NewHelmTemplater creates a new Helm templater\nfunc NewHelmTemplater(detectedPrefix, chartName, managerNamespace string) *HelmTemplater {\n\treturn &HelmTemplater{\n\t\tdetectedPrefix:   detectedPrefix,\n\t\tchartName:        chartName,\n\t\tmanagerNamespace: managerNamespace,\n\t}\n}\n\n// getDefaultContainerName extracts the container name from kubectl.kubernetes.io/default-container annotation.\n// This allows the Helm plugin to work with any container name, not just \"manager\".\n// If the annotation is not found, it falls back to \"manager\" for backward compatibility.\nfunc (t *HelmTemplater) getDefaultContainerName(yamlContent string) string {\n\t// Look for kubectl.kubernetes.io/default-container annotation\n\tpattern := regexp.MustCompile(`kubectl\\.kubernetes\\.io/default-container:\\s+(\\S+)`)\n\tmatches := pattern.FindStringSubmatch(yamlContent)\n\tif len(matches) > 1 {\n\t\treturn matches[1]\n\t}\n\t// Fallback to \"manager\" for backward compatibility with older scaffolds\n\treturn \"manager\"\n}\n\n// resourceNameTemplate creates a Helm template for a resource name with 63-char safety.\n// Uses <chartname>.resourceName helper which intelligently truncates when base + suffix > 63 chars.\n// Template name is scoped to the chart to prevent collisions when used as a Helm dependency.\nfunc (t *HelmTemplater) resourceNameTemplate(suffix string) string {\n\treturn `{{ include \"` + t.chartName + `.resourceName\" (dict \"suffix\" \"` + suffix + `\" \"context\" $) }}`\n}\n\n// ApplyHelmSubstitutions converts YAML content to use Helm template syntax\nfunc (t *HelmTemplater) ApplyHelmSubstitutions(yamlContent string, resource *unstructured.Unstructured) string {\n\t// Escape existing Go template syntax ({{ }}) FIRST before adding Helm templates.\n\t// Resources from install.yaml may contain templates that should be preserved as literal text.\n\t// For example: CRD default values, ConfigMap data, Secret URLs, annotations, etc.\n\tyamlContent = t.escapeExistingTemplateSyntax(yamlContent)\n\n\t// Apply conditional wrappers first\n\tyamlContent = t.addConditionalWrappers(yamlContent, resource)\n\n\t// Apply general project name substitutions\n\tyamlContent = t.substituteProjectNames(yamlContent, resource)\n\n\t// Apply namespace substitutions\n\tyamlContent = t.substituteNamespace(yamlContent, resource)\n\n\t// Apply cert-manager and webhook-specific templating AFTER other substitutions\n\tyamlContent = t.substituteCertManagerReferences(yamlContent, resource)\n\n\tyamlContent = t.substituteResourceNamesWithPrefix(yamlContent, resource)\n\n\t// Apply labels and annotations from Helm chart\n\tyamlContent = t.addHelmLabelsAndAnnotations(yamlContent, resource)\n\n\t// Apply resource-specific substitutions\n\tyamlContent = t.substituteRBACValues(yamlContent)\n\n\t// Apply deployment-specific templating\n\tif resource.GetKind() == kindDeployment {\n\t\tyamlContent = t.templateDeploymentFields(yamlContent)\n\n\t\t// Apply conditional logic for cert-manager related fields in deployments\n\t\tyamlContent = t.makeContainerArgsConditional(yamlContent)\n\t\tyamlContent = t.makeWebhookVolumeMountsConditional(yamlContent)\n\t\tyamlContent = t.makeWebhookVolumesConditional(yamlContent)\n\t\tyamlContent = t.makeMetricsVolumeMountsConditional(yamlContent)\n\t\tyamlContent = t.makeMetricsVolumesConditional(yamlContent)\n\t}\n\n\t// Apply port templating for Services and Deployments\n\tif resource.GetKind() == kindService || resource.GetKind() == kindDeployment {\n\t\tyamlContent = t.templatePorts(yamlContent, resource)\n\t}\n\n\t// Final tidy-up: avoid accidental blank lines after Helm if-block starts\n\t// Some replacements may introduce an empty line between a `{{- if ... }}`\n\t// and the following content; collapse that to ensure consistent formatting.\n\tyamlContent = t.collapseBlankLineAfterIf(yamlContent)\n\n\treturn yamlContent\n}\n\n// escapeExistingTemplateSyntax escapes Go template syntax ({{ }}) in YAML to prevent\n// Helm from parsing them. Converts existing templates to literal strings that Helm outputs as-is.\n//\n// Why this is needed:\n// Resources from install.yaml may contain {{ }} in string fields that are NOT Helm templates.\n// Without escaping, Helm will try to evaluate them and fail. For example:\n//\n//\tCRD default: \"Branch: {{ .Spec.Branch }}\"  ->  ERROR: .Spec undefined\n//\n// How it works:\n// Wraps non-Helm templates in string literals so Helm outputs them unchanged:\n//\n//\t{{ .Field }}  ->  {{ \"{{ .Field }}\" }}\n//\n// When Helm renders this, it outputs the literal string: {{ .Field }}\n//\n// Smart detection:\n// Only escapes templates that DON'T start with Helm keywords:\n//   - .Release, .Values, .Chart (Helm built-ins)\n//   - include, if, with, range, toYaml (Helm functions)\n//\n// This means our Helm templates work normally while existing templates are preserved.\nfunc (t *HelmTemplater) escapeExistingTemplateSyntax(yamlContent string) string {\n\t// (?s) makes '.' match newlines so split-line templates produced by sigs.k8s.io/yaml's\n\t// ~80-column folding (e.g. \"{{ .LongName\\n    }}\") are matched in a single pass.\n\ttemplatePattern := regexp.MustCompile(`(?s)\\{\\{(.*?)\\}\\}`)\n\n\tyamlContent = templatePattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t// Extract content between {{ and }}\n\t\tcontent := strings.TrimPrefix(match, \"{{\")\n\t\tcontent = strings.TrimSuffix(content, \"}}\")\n\t\ttrimmedContent := strings.TrimSpace(content)\n\n\t\t// Check if this is a Helm template (starts with Helm keyword)\n\t\thelmPatterns := []string{\n\t\t\t\"include \", \"- include \",\n\t\t\t\".Release.\", \"- .Release.\",\n\t\t\t\".Values.\", \"- .Values.\",\n\t\t\t\".Chart.\", \"- .Chart.\",\n\t\t\t\"toYaml \", \"- toYaml \",\n\t\t\t\"if \", \"- if \",\n\t\t\t\"end \", \"- end \",\n\t\t\t\"with \", \"- with \",\n\t\t\t\"range \", \"- range \",\n\t\t\t\"else\", \"- else\",\n\t\t}\n\n\t\t// If it's a Helm template, keep it as-is\n\t\tfor _, pattern := range helmPatterns {\n\t\t\tif strings.HasPrefix(trimmedContent, pattern) {\n\t\t\t\treturn match\n\t\t\t}\n\t\t}\n\n\t\t// Otherwise, escape it to preserve as literal text\n\t\t// Collapse any newline+indent that sigs.k8s.io/yaml may have introduced via line-wrapping.\n\t\tcollapsed := regexp.MustCompile(`\\n[ \\t]+`).ReplaceAllString(content, \" \")\n\n\t\t// Before re-escaping for Go template string literals, unescape any YAML double-quoted\n\t\t// scalar escape sequences. yaml.Marshal emits \\\" for a literal \" inside a double-quoted\n\t\t// YAML scalar; without this step the subsequent \"→\\\" replacement double-escapes them to\n\t\t// \\\\\" which breaks Helm's Go template parser: \\\\ becomes one backslash, then the next \"\n\t\t// closes the string prematurely, leaving tokens like \"asset-id\" outside where \"-\" is a\n\t\t// bad character (U+002D).\n\t\tunescaped := strings.ReplaceAll(collapsed, `\\\"`, `\"`)\n\t\tescapedContent := strings.ReplaceAll(unescaped, `\"`, `\\\"`)\n\n\t\t// Wrap in Helm string literal: {{ \"{{...}}\" }}\n\t\treturn `{{ \"{{` + escapedContent + `}}\" }}`\n\t})\n\n\treturn yamlContent\n}\n\n// substituteProjectNames keeps original YAML as much as possible - only add Helm templating\nfunc (t *HelmTemplater) substituteProjectNames(yamlContent string, _ *unstructured.Unstructured) string {\n\treturn yamlContent\n}\n\n// substituteNamespace replaces manager namespace references with {{ .Release.Namespace }}\n// while preserving cross-namespace references (e.g., infrastructure, production).\n//\n// DESIGN RATIONALE:\n// We use regex-based replacement (not YAML parsing) because the content already contains\n// Helm templates from previous substitutions, which would break YAML parsing.\n//\n// SAFETY GUARANTEES:\n// 1. Namespace fields: Only replaces `namespace: <exact-value>` (line-anchored regex)\n// 2. DNS names: Only replaces `.<namespace>.` (dots on both sides prevent substring matches)\n// 3. References: Only replaces `<namespace>/` (word boundary prevents false matches)\n//\n// TESTED SCENARIOS:\n// - All standard K8s resource types (ConfigMap, Secret, Ingress, etc.)\n// - All monitoring resources (ServiceMonitor, PodMonitor)\n// - All RBAC resources (Role, RoleBinding, with cross-namespace support)\n// - All DNS patterns (.svc, .svc.cluster.local, .pod, .endpoints)\n// - Custom CRDs with any structure\n// - Cross-namespace preservation (infrastructure, production, etc.)\n// - Substring bug prevention (namespace \"user\" doesn't break resource \"users\")\nfunc (t *HelmTemplater) substituteNamespace(yamlContent string, resource *unstructured.Unstructured) string {\n\tmanagerNamespace := t.managerNamespace\n\tnamespaceTemplate := \"{{ .Release.Namespace }}\"\n\n\t// 1. NAMESPACE FIELDS: Replace `namespace: <manager-namespace>`\n\t//    Pattern: Line-anchored to prevent false matches\n\t//    Example: `namespace: project-system` → `namespace: {{ .Release.Namespace }}`\n\tnamespaceFieldPattern := regexp.MustCompile(`(?m)^(\\s*)namespace:\\s+` + regexp.QuoteMeta(managerNamespace) + `\\s*$`)\n\tyamlContent = namespaceFieldPattern.ReplaceAllString(yamlContent, \"${1}namespace: \"+namespaceTemplate)\n\n\t// 2. RESOURCE REFERENCES: Replace `<manager-namespace>/resource-name`\n\t//    Pattern: Word boundary ensures we don't match partial words\n\t//    Example: `cert-manager.io/inject-ca-from: project-system/cert` → `{{ .Release.Namespace }}/cert`\n\t//    Example: `configMapRef: project-system/config` → `{{ .Release.Namespace }}/config`\n\trefPattern := regexp.MustCompile(`\\b` + regexp.QuoteMeta(managerNamespace) + `/`)\n\tyamlContent = refPattern.ReplaceAllString(yamlContent, namespaceTemplate+\"/\")\n\n\t// 3. DNS NAMES: Replace `.<manager-namespace>.` in Kubernetes DNS patterns\n\t//    Pattern: Dots on both sides ensure we only match DNS, not arbitrary strings\n\t//    Handles ALL K8s DNS patterns: .svc, .svc.cluster.local, .pod, .endpoints, etc.\n\t//    Example: `service.project-system.svc` → `service.{{ .Release.Namespace }}.svc`\n\t//    Example: `pod.project-system.pod.cluster.local` → `pod.{{ .Release.Namespace }}.pod.cluster.local`\n\t//\n\t//    SAFETY: This won't match:\n\t//    - Resource names: \"users\" (no dots around it)\n\t//    - Arbitrary strings: \"my-application\" (no dots)\n\t//    - Labels: \"app=project-system\" (no dots on both sides)\n\tdnsPattern := regexp.MustCompile(`\\.` + regexp.QuoteMeta(managerNamespace) + `\\.`)\n\tyamlContent = dnsPattern.ReplaceAllString(yamlContent, \".\"+namespaceTemplate+\".\")\n\n\t// 4. CERTIFICATE-SPECIFIC: Additional service name templating for cert-manager\n\t//    This is additive only and doesn't interfere with the above replacements\n\tif resource.GetKind() == kindCertificate {\n\t\tyamlContent = t.substituteCertificateDNSNames(yamlContent, resource)\n\t}\n\n\treturn yamlContent\n}\n\n// substituteCertificateDNSNames replaces hardcoded DNS names in certificates with proper service templates\nfunc (t *HelmTemplater) substituteCertificateDNSNames(yamlContent string, resource *unstructured.Unstructured) string {\n\tname := resource.GetName()\n\n\t// Replace service names with templated ones based on certificate type\n\tif strings.Contains(name, \"metrics-cert\") || strings.Contains(name, \"metrics\") {\n\t\t// Metrics certificates should point to metrics service\n\t\t// Use chart-specific resourceName helper for consistent naming with 63-char safety\n\t\tmetricsServiceTemplate := \"{{ include \\\"\" + t.chartName + \".resourceName\\\" \" +\n\t\t\t\"(dict \\\"suffix\\\" \\\"controller-manager-metrics-service\\\" \\\"context\\\" $) }}\"\n\t\tmetricsServiceFQDN := metricsServiceTemplate + \".{{ include \\\"\" + t.chartName + \".namespaceName\\\" $ }}.svc\"\n\t\tmetricsServiceFQDNCluster := metricsServiceTemplate +\n\t\t\t\".{{ include \\\"\" + t.chartName + \".namespaceName\\\" $ }}.svc.cluster.local\"\n\n\t\t// Replace placeholders\n\t\tyamlContent = strings.ReplaceAll(yamlContent, \"SERVICE_NAME.SERVICE_NAMESPACE.svc\", metricsServiceFQDN)\n\t\tyamlContent = strings.ReplaceAll(yamlContent,\n\t\t\t\"SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\", metricsServiceFQDNCluster)\n\n\t\t// Also replace hardcoded service names\n\t\thardcodedMetricsService := t.detectedPrefix + \"-controller-manager-metrics-service\"\n\t\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedMetricsService, metricsServiceTemplate)\n\t} else if strings.Contains(name, \"serving-cert\") || strings.Contains(name, \"webhook\") {\n\t\thardcodedWebhookServiceShort := t.detectedPrefix + \"-webhook-service\"\n\t\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedWebhookServiceShort, t.resourceNameTemplate(\"webhook-service\"))\n\t}\n\n\treturn yamlContent\n}\n\n// substituteCertManagerReferences applies cert-manager specific template substitutions\nfunc (t *HelmTemplater) substituteCertManagerReferences(\n\tyamlContent string,\n\tresource *unstructured.Unstructured,\n) string {\n\tkind := resource.GetKind()\n\n\tif kind == kindIssuer || kind == kindCertificate {\n\t\thardcodedIssuerRef := t.detectedPrefix + \"-selfsigned-issuer\"\n\t\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedIssuerRef, t.resourceNameTemplate(\"selfsigned-issuer\"))\n\t}\n\n\tif kind == kindValidatingWebhook || kind == kindMutatingWebhook || kind == kindCRD {\n\t\thardcodedService := \"name: \" + t.detectedPrefix + \"-webhook-service\"\n\t\ttemplatedService := \"name: \" + t.resourceNameTemplate(\"webhook-service\")\n\t\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedService, templatedService)\n\t}\n\n\tyamlContent = t.substituteCertManagerAnnotations(yamlContent)\n\treturn yamlContent\n}\n\n// substituteResourceNamesWithPrefix templates ALL resource names using chart.serviceName helper.\n// Generic regex-based approach works for any resource type without hardcoding specific names.\n// Excludes container names since those are internal pod identifiers that don't need templating.\nfunc (t *HelmTemplater) substituteResourceNamesWithPrefix(yamlContent string, _ *unstructured.Unstructured) string {\n\tnamePattern := regexp.MustCompile(\n\t\t`(\\s+)([a-zA-Z]*[Nn]ame):\\s+` + regexp.QuoteMeta(t.detectedPrefix) + `(-[a-zA-Z0-9-]+)`)\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tresult := make([]string, 0, len(lines))\n\n\tfor i, line := range lines {\n\t\tif !namePattern.MatchString(line) {\n\t\t\tresult = append(result, line)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check if this is a container name by looking at surrounding context\n\t\t// Container names appear after \"containers:\" and before other container fields (image, args, etc.)\n\t\tisContainerName := false\n\t\tif strings.Contains(line, \"name:\") {\n\t\t\t// Look backward for \"containers:\" within ~20 lines\n\t\t\tfor j := i - 1; j >= 0 && j >= i-20; j-- {\n\t\t\t\ttrimmed := strings.TrimSpace(lines[j])\n\t\t\t\tif trimmed == \"containers:\" {\n\t\t\t\t\tisContainerName = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\t// Stop if we hit a new top-level section\n\t\t\t\tif strings.HasPrefix(lines[j], \"  \") && strings.HasSuffix(trimmed, \":\") &&\n\t\t\t\t\t(trimmed == \"spec:\" || trimmed == \"template:\" || trimmed == \"volumes:\") {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif isContainerName {\n\t\t\t// Don't template container names - keep them as-is\n\t\t\tresult = append(result, line)\n\t\t} else {\n\t\t\t// Template other resource names\n\t\t\ttemplatedLine := namePattern.ReplaceAllStringFunc(line, func(match string) string {\n\t\t\t\tparts := namePattern.FindStringSubmatch(match)\n\t\t\t\tif len(parts) < 4 {\n\t\t\t\t\treturn match\n\t\t\t\t}\n\n\t\t\t\tindent := parts[1]\n\t\t\t\tfieldName := parts[2]\n\t\t\t\tsuffix := parts[3][1:] // Remove leading dash\n\n\t\t\t\treturn indent + fieldName + \": \" + t.resourceNameTemplate(suffix)\n\t\t\t})\n\t\t\tresult = append(result, templatedLine)\n\t\t}\n\t}\n\n\treturn strings.Join(result, \"\\n\")\n}\n\n// addHelmLabelsAndAnnotations replaces kustomize managed-by labels with Helm equivalents\nfunc (t *HelmTemplater) addHelmLabelsAndAnnotations(\n\tyamlContent string,\n\tresource *unstructured.Unstructured,\n) string {\n\t// Replace app.kubernetes.io/managed-by: kustomize with Helm template\n\t// Use regex to handle different whitespace patterns\n\tmanagedByRegex := regexp.MustCompile(`(\\s*)app\\.kubernetes\\.io/managed-by:\\s+kustomize`)\n\tyamlContent = managedByRegex.ReplaceAllString(yamlContent, \"${1}app.kubernetes.io/managed-by: {{ .Release.Service }}\")\n\n\thardcodedNameLabel := \"app.kubernetes.io/name: \" + t.detectedPrefix\n\ttemplatedNameLabel := \"app.kubernetes.io/name: {{ include \\\"\" + t.chartName + \".name\\\" . }}\"\n\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedNameLabel, templatedNameLabel)\n\n\t// Add standard Helm labels to metadata.labels and selectors\n\tyamlContent = t.addStandardHelmLabels(yamlContent, resource)\n\n\treturn yamlContent\n}\n\n// checkExistingLabels checks if standard Helm labels already exist in a labels section\n// by looking both backward and forward from the current position\nfunc checkExistingLabels(lines []string, currentIndex int, indent string) (hasChart, hasInstance, hasManagedBy bool) {\n\t// Look backward from current position (managed-by often appears before name in kustomize output)\n\tfor j := currentIndex - 1; j >= 0 && j >= currentIndex-10; j-- {\n\t\tbackLine := lines[j]\n\t\tbackTrimmed := strings.TrimSpace(backLine)\n\t\tbackIndent, _ := leadingWhitespace(backLine)\n\n\t\t// Stop if we've moved out of the labels section\n\t\tif backTrimmed == \"labels:\" {\n\t\t\tbreak\n\t\t}\n\t\tif backTrimmed != \"\" && len(backIndent) < len(indent) {\n\t\t\tbreak\n\t\t}\n\n\t\tif strings.Contains(backLine, \"helm.sh/chart:\") {\n\t\t\thasChart = true\n\t\t}\n\t\tif strings.Contains(backLine, \"app.kubernetes.io/instance:\") {\n\t\t\thasInstance = true\n\t\t}\n\t\tif strings.Contains(backLine, \"app.kubernetes.io/managed-by:\") {\n\t\t\thasManagedBy = true\n\t\t}\n\t}\n\n\t// Look ahead from current position\n\tfor j := currentIndex + 1; j < len(lines) && j < currentIndex+10; j++ {\n\t\tnextLine := lines[j]\n\t\tnextTrimmed := strings.TrimSpace(nextLine)\n\t\tnextIndent, _ := leadingWhitespace(nextLine)\n\n\t\t// Stop if we've moved to a new section\n\t\tif nextTrimmed != \"\" && len(nextIndent) < len(indent) {\n\t\t\tbreak\n\t\t}\n\n\t\tif strings.Contains(nextLine, \"helm.sh/chart:\") {\n\t\t\thasChart = true\n\t\t}\n\t\tif strings.Contains(nextLine, \"app.kubernetes.io/instance:\") {\n\t\t\thasInstance = true\n\t\t}\n\t\tif strings.Contains(nextLine, \"app.kubernetes.io/managed-by:\") {\n\t\t\thasManagedBy = true\n\t\t}\n\t}\n\n\treturn hasChart, hasInstance, hasManagedBy\n}\n\n// addStandardHelmLabels adds standard Helm labels (helm.sh/chart, app.kubernetes.io/instance,\n// and app.kubernetes.io/managed-by) to all labels sections except selectors (which must be immutable)\nfunc (t *HelmTemplater) addStandardHelmLabels(yamlContent string, _ *unstructured.Unstructured) string {\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tresult := make([]string, 0, len(lines)+10) // Pre-allocate with extra space for added labels\n\tinSelector := false\n\n\tfor i := range lines {\n\t\tline := lines[i]\n\t\tresult = append(result, line)\n\n\t\t// Track if we're in a selector section (matchLabels or spec.selector for Services)\n\t\ttrimmed := strings.TrimSpace(line)\n\t\tisMatchLabels := trimmed == \"matchLabels:\"\n\t\tisSelectorWithoutMatchLabels := trimmed == \"selector:\" && i+1 < len(lines) &&\n\t\t\t!strings.Contains(lines[i+1], \"matchLabels\")\n\t\tif isMatchLabels || isSelectorWithoutMatchLabels {\n\t\t\tinSelector = true\n\t\t}\n\n\t\t// Exit selector section when we hit a line with less indentation\n\t\tif inSelector && trimmed != \"\" && !strings.HasPrefix(trimmed, \"app.kubernetes.io/\") &&\n\t\t\t!strings.HasPrefix(trimmed, \"control-plane:\") && strings.Contains(trimmed, \":\") {\n\t\t\tinSelector = false\n\t\t}\n\n\t\t// Add standard Helm labels to any labels section (metadata.labels, template.metadata.labels)\n\t\t// but NOT to selectors (which must remain immutable)\n\t\tif !inSelector && strings.Contains(line, \"app.kubernetes.io/name:\") {\n\t\t\tindent, _ := leadingWhitespace(line)\n\n\t\t\t// Check if we're in a labels section by looking backwards\n\t\t\tisInLabelsSection := false\n\t\t\tfor j := i - 1; j >= 0 && j >= i-5; j-- {\n\t\t\t\tif strings.TrimSpace(lines[j]) == \"labels:\" {\n\t\t\t\t\tisInLabelsSection = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif strings.TrimSpace(lines[j]) == \"metadata:\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isInLabelsSection {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Check if standard labels already exist in this labels section\n\t\t\thasHelmChart, hasInstance, hasManagedBy := checkExistingLabels(lines, i, indent)\n\n\t\t\t// Add helm.sh/chart if it doesn't exist\n\t\t\tif !hasHelmChart {\n\t\t\t\tresult = append(result, indent+\"helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \\\"+\\\" \\\"_\\\" }}\")\n\t\t\t}\n\n\t\t\t// Add app.kubernetes.io/instance if it doesn't exist\n\t\t\tif !hasInstance {\n\t\t\t\tresult = append(result, indent+\"app.kubernetes.io/instance: {{ .Release.Name }}\")\n\t\t\t}\n\n\t\t\t// Add app.kubernetes.io/managed-by if it doesn't exist (per Helm best practices)\n\t\t\tif !hasManagedBy {\n\t\t\t\tresult = append(result, indent+\"app.kubernetes.io/managed-by: {{ .Release.Service }}\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn strings.Join(result, \"\\n\")\n}\n\n// substituteRBACValues applies RBAC-specific template substitutions\nfunc (t *HelmTemplater) substituteRBACValues(yamlContent string) string {\n\troleRefBlockPattern := regexp.MustCompile(\n\t\t`(?s)(roleRef:\\s*\\n(?:\\s+\\w+:.*\\n)*?)(\\s+)name:\\s+` +\n\t\t\tregexp.QuoteMeta(t.detectedPrefix) + `-manager-role`)\n\tyamlContent = roleRefBlockPattern.ReplaceAllString(\n\t\tyamlContent, `${1}${2}name: `+t.resourceNameTemplate(\"manager-role\"))\n\n\troleRefBlockPatternSimple := regexp.MustCompile(\n\t\t`(?s)(roleRef:\\s*\\n(?:\\s+\\w+:.*\\n)*?)(\\s+)name:\\s+manager-role`)\n\tyamlContent = roleRefBlockPatternSimple.ReplaceAllString(\n\t\tyamlContent, `${1}${2}name: `+t.resourceNameTemplate(\"manager-role\"))\n\n\treturn yamlContent\n}\n\n// substituteCertManagerAnnotations replaces hardcoded certificate references in annotations\nfunc (t *HelmTemplater) substituteCertManagerAnnotations(yamlContent string) string {\n\thardcodedServingCert := t.detectedPrefix + \"-serving-cert\"\n\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedServingCert, t.resourceNameTemplate(\"serving-cert\"))\n\n\thardcodedMetricsCert := t.detectedPrefix + \"-metrics-certs\"\n\tyamlContent = strings.ReplaceAll(yamlContent, hardcodedMetricsCert, t.resourceNameTemplate(\"metrics-certs\"))\n\n\treturn yamlContent\n}\n\n// templateDeploymentFields converts deployment-specific fields to Helm templates\nfunc (t *HelmTemplater) templateDeploymentFields(yamlContent string) string {\n\t// Template replicas from values.yaml (manager.replicas)\n\tyamlContent = t.templateReplicas(yamlContent)\n\t// Template configuration fields\n\tyamlContent = t.templateImageReference(yamlContent)\n\tyamlContent = t.templateEnvironmentVariables(yamlContent)\n\tyamlContent = t.templateImagePullSecrets(yamlContent)\n\tyamlContent = t.templatePodSecurityContext(yamlContent)\n\tyamlContent = t.templateContainerSecurityContext(yamlContent)\n\tyamlContent = t.templateResources(yamlContent)\n\tyamlContent = t.templateSecurityContexts(yamlContent)\n\tyamlContent = t.templateVolumeMounts(yamlContent)\n\tyamlContent = t.templateVolumes(yamlContent)\n\tyamlContent = t.templateControllerManagerArgs(yamlContent)\n\tyamlContent = t.templateBasicWithStatement(\n\t\tyamlContent,\n\t\t\"nodeSelector\",\n\t\t\"spec.template.spec\",\n\t\t\".Values.manager.nodeSelector\",\n\t)\n\tyamlContent = t.templateBasicWithStatement(\n\t\tyamlContent,\n\t\t\"affinity\",\n\t\t\"spec.template.spec\",\n\t\t\".Values.manager.affinity\",\n\t)\n\tyamlContent = t.templateBasicWithStatement(\n\t\tyamlContent,\n\t\t\"tolerations\",\n\t\t\"spec.template.spec\",\n\t\t\".Values.manager.tolerations\",\n\t)\n\n\treturn yamlContent\n}\n\n// templateReplicas replaces deployment spec.replicas with .Values.manager.replicas so the\n// value in values.yaml is used. With leader election, only one replica is active at a time;\n// multiple replicas are valid for HA (standby replicas).\nfunc (t *HelmTemplater) templateReplicas(yamlContent string) string {\n\tif strings.Contains(yamlContent, \".Values.manager.replicas\") {\n\t\treturn yamlContent\n\t}\n\t// Match a line that is exactly \"  replicas: <digits>\" (deployment spec.replicas).\n\t// Preserve indentation so the replacement fits the existing YAML structure.\n\treplicasPattern := regexp.MustCompile(`(?m)^(\\s*)replicas:\\s*\\d+\\s*$`)\n\treturn replicasPattern.ReplaceAllString(yamlContent, \"${1}replicas: {{ .Values.manager.replicas }}\")\n}\n\n// templateEnvironmentVariables exposes environment variables via values.yaml\nfunc (t *HelmTemplater) templateEnvironmentVariables(yamlContent string) string {\n\tcontainerName := t.getDefaultContainerName(yamlContent)\n\t// Check for both literal container name and templated container name\n\thasLiteralName := strings.Contains(yamlContent, \"name: \"+containerName)\n\thasTemplatedName := strings.Contains(yamlContent, `name: {{ include \"`) && strings.Contains(yamlContent, `\"manager\"`)\n\tif !hasLiteralName && !hasTemplatedName {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := range lines {\n\t\tif strings.TrimSpace(lines[i]) != \"env:\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\ttrimmed := strings.TrimSpace(lines[end])\n\t\t\tif trimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent < indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif lineIndent == indentLen && !strings.HasPrefix(trimmed, \"-\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tnextLine := \"\"\n\t\tif i+1 < len(lines) {\n\t\t\tnextLine = lines[i+1]\n\t\t}\n\t\tif strings.Contains(nextLine, \".Values.manager.env\") || strings.Contains(nextLine, \"envOverrides\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tchildIndent := indentStr + \"  \"\n\t\tchildIndentWidth := strconv.Itoa(len(childIndent))\n\t\t// Env list + envOverrides (CLI --set). Secret refs go in env list.\n\t\thasEnv := `{{- if or .Values.manager.env (and (kindIs \"map\" .Values.manager.envOverrides) ` +\n\t\t\t`(not (empty .Values.manager.envOverrides))) }}`\n\t\tblock := make([]string, 0, 22)\n\t\tblock = append(block,\n\t\t\tindentStr+\"env:\",\n\t\t\thasEnv,\n\t\t\tchildIndent+`{{- if .Values.manager.env }}`,\n\t\t\tchildIndent+\"{{- toYaml .Values.manager.env | nindent \"+childIndentWidth+\" }}\",\n\t\t\tchildIndent+`{{- end }}`,\n\t\t\tchildIndent+`{{- if kindIs \"map\" .Values.manager.envOverrides }}`,\n\t\t\tchildIndent+`{{- range $k, $v := .Values.manager.envOverrides }}`,\n\t\t\tchildIndent+`- name: {{ $k }}`,\n\t\t\tchildIndent+`  value: {{ $v | quote }}`,\n\t\t\tchildIndent+`{{ end }}`,\n\t\t\tchildIndent+`{{- end }}`,\n\t\t\tchildIndent+`{{- else }}`,\n\t\t\tchildIndent+\"[]\",\n\t\t\tchildIndent+`{{- end }}`,\n\t\t)\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, block...)\n\t\tnewLines = append(newLines, lines[end:]...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\n// templateResources converts resource sections to Helm templates\nfunc (t *HelmTemplater) templateResources(yamlContent string) string {\n\tcontainerName := t.getDefaultContainerName(yamlContent)\n\t// Check for both literal container name and templated container name\n\thasLiteralName := strings.Contains(yamlContent, \"name: \"+containerName)\n\thasTemplatedName := strings.Contains(yamlContent, `name: {{ include \"`) && strings.Contains(yamlContent, `\"manager\"`)\n\tif (!hasLiteralName && !hasTemplatedName) || !strings.Contains(yamlContent, \"resources:\") {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := range lines {\n\t\tif strings.TrimSpace(lines[i]) != \"resources:\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\ttrimmed := strings.TrimSpace(lines[end])\n\t\t\tif trimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent < indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// stop at same-level keys that are not part of the resources mapping\n\t\t\tif lineIndent == indentLen && !strings.Contains(trimmed, \":\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif lineIndent == indentLen && strings.HasSuffix(trimmed, \":\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif i+1 < len(lines) && strings.Contains(lines[i+1], \".Values.manager.resources\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tchildIndent := indentStr + \"  \"\n\t\tchildIndentWidth := strconv.Itoa(len(childIndent))\n\n\t\tblock := []string{\n\t\t\tindentStr + \"resources:\",\n\t\t\tchildIndent + \"{{- if .Values.manager.resources }}\",\n\t\t\tchildIndent + \"{{- toYaml .Values.manager.resources | nindent \" + childIndentWidth + \" }}\",\n\t\t\tchildIndent + \"{{- else }}\",\n\t\t\tchildIndent + \"{}\",\n\t\t\tchildIndent + \"{{- end }}\",\n\t\t}\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, block...)\n\t\tnewLines = append(newLines, lines[end:]...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\n// templateSecurityContexts preserves security contexts from kustomize output\nfunc (t *HelmTemplater) templateSecurityContexts(yamlContent string) string {\n\t// Security contexts are preserved as-is from the kustomize output to maintain\n\t// the exact security configuration without interfering with other container fields\n\treturn yamlContent\n}\n\n// templateVolumeMounts converts volumeMounts sections to keep them as-is since they're webhook-specific\nfunc (t *HelmTemplater) templateVolumeMounts(yamlContent string) string {\n\t// For webhook volumeMounts, we keep them as-is since they're required for webhook functionality\n\t// They will be conditionally included based on webhook configuration\n\treturn yamlContent\n}\n\n// templateVolumes converts volumes sections to keep them as-is since they're webhook-specific\nfunc (t *HelmTemplater) templateVolumes(yamlContent string) string {\n\t// For webhook volumes, we keep them as-is since they're required for webhook functionality\n\t// They will be conditionally included based on webhook configuration\n\treturn yamlContent\n}\n\n// templateImagePullSecrets exposes imagePullSecrets via values.yaml\nfunc (t *HelmTemplater) templateImagePullSecrets(yamlContent string) string {\n\tif !strings.Contains(yamlContent, \"imagePullSecrets:\") {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := range lines {\n\t\t// Use prefix to allow `imagePullSecrets: []` to be preserved\n\t\tif !strings.HasPrefix(strings.TrimSpace(lines[i]), \"imagePullSecrets:\") {\n\t\t\tcontinue\n\t\t}\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\ttrimmed := strings.TrimSpace(lines[end])\n\t\t\tif trimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent < indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif lineIndent == indentLen && !strings.HasPrefix(trimmed, \"-\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif i+1 < len(lines) && strings.Contains(lines[i+1], \".Values.manager.imagePullSecrets\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tchildIndent := indentStr + \"  \"\n\t\tchildIndentWidth := strconv.Itoa(len(childIndent))\n\n\t\tblock := []string{\n\t\t\tindentStr + \"{{- if .Values.manager.imagePullSecrets }}\",\n\t\t\tindentStr + \"imagePullSecrets:\",\n\t\t\tchildIndent + \"{{- toYaml .Values.manager.imagePullSecrets | nindent \" + childIndentWidth + \" }}\",\n\t\t\tindentStr + \"{{- end }}\",\n\t\t}\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, block...)\n\t\tnewLines = append(newLines, lines[end:]...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\n// templatePodSecurityContext exposes podSecurityContext via values.yaml\nfunc (t *HelmTemplater) templatePodSecurityContext(yamlContent string) string {\n\tif !strings.Contains(yamlContent, \"securityContext:\") {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := range lines {\n\t\tif strings.TrimSpace(lines[i]) != \"securityContext:\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\ttrimmed := strings.TrimSpace(lines[end])\n\t\t\tif trimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent <= indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif end >= len(lines) {\n\t\t\tbreak\n\t\t}\n\n\t\tif !strings.HasPrefix(strings.TrimSpace(lines[end]), \"serviceAccountName:\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tif i+1 < len(lines) && strings.Contains(lines[i+1], \".Values.manager.podSecurityContext\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tchildIndent := indentStr + \"  \"\n\t\tchildIndentWidth := strconv.Itoa(len(childIndent))\n\n\t\tblock := []string{\n\t\t\tindentStr + \"securityContext:\",\n\t\t\tchildIndent + \"{{- if .Values.manager.podSecurityContext }}\",\n\t\t\tchildIndent + \"{{- toYaml .Values.manager.podSecurityContext | nindent \" + childIndentWidth + \" }}\",\n\t\t\tchildIndent + \"{{- else }}\",\n\t\t\tchildIndent + \"{}\",\n\t\t\tchildIndent + \"{{- end }}\",\n\t\t}\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, block...)\n\t\tnewLines = append(newLines, lines[end:]...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\n// templateContainerSecurityContext exposes container securityContext via values.yaml\nfunc (t *HelmTemplater) templateContainerSecurityContext(yamlContent string) string {\n\tcontainerName := t.getDefaultContainerName(yamlContent)\n\t// Check for both literal container name and templated container name\n\thasLiteralName := strings.Contains(yamlContent, \"name: \"+containerName)\n\thasTemplatedName := strings.Contains(yamlContent, `name: {{ include \"`) && strings.Contains(yamlContent, `\"manager\"`)\n\tif (!hasLiteralName && !hasTemplatedName) || !strings.Contains(yamlContent, \"securityContext:\") {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := range lines {\n\t\tif strings.TrimSpace(lines[i]) != \"securityContext:\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\ttrimmed := strings.TrimSpace(lines[end])\n\t\t\tif trimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent <= indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif end >= len(lines) {\n\t\t\tbreak\n\t\t}\n\n\t\tif strings.HasPrefix(strings.TrimSpace(lines[end]), \"serviceAccountName:\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tlookAheadEnd := min(end+5, len(lines))\n\t\tjoined := strings.Join(lines[i:lookAheadEnd], \"\\n\")\n\t\tif strings.Contains(joined, \".Values.manager.securityContext\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tchildIndent := indentStr + \"  \"\n\t\tchildIndentWidth := strconv.Itoa(len(childIndent))\n\n\t\tblock := []string{\n\t\t\tindentStr + \"securityContext:\",\n\t\t\tchildIndent + \"{{- if .Values.manager.securityContext }}\",\n\t\t\tchildIndent + \"{{- toYaml .Values.manager.securityContext | nindent \" + childIndentWidth + \" }}\",\n\t\t\tchildIndent + \"{{- else }}\",\n\t\t\tchildIndent + \"{}\",\n\t\t\tchildIndent + \"{{- end }}\",\n\t\t}\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, block...)\n\t\tnewLines = append(newLines, lines[end:]...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\nfunc leadingWhitespace(line string) (string, int) {\n\ttrimmed := strings.TrimLeft(line, \" \\t\")\n\tindentLen := len(line) - len(trimmed)\n\treturn line[:indentLen], indentLen\n}\n\n// templateControllerManagerArgs exposes controller manager args via values.yaml while keeping core defaults\nfunc (t *HelmTemplater) templateControllerManagerArgs(yamlContent string) string {\n\tcontainerName := t.getDefaultContainerName(yamlContent)\n\t// Check for both literal container name and templated container name\n\thasLiteralName := strings.Contains(yamlContent, \"name: \"+containerName)\n\thasTemplatedName := strings.Contains(yamlContent, `name: {{ include \"`) && strings.Contains(yamlContent, `\"manager\"`)\n\tif !hasLiteralName && !hasTemplatedName {\n\t\treturn yamlContent\n\t}\n\n\targsPattern := regexp.MustCompile(`(?m)([ \\t]+)args:\\n((?:[ \\t]+-.*\\n)+)`)\n\tloc := argsPattern.FindStringSubmatchIndex(yamlContent)\n\tif loc == nil {\n\t\treturn yamlContent\n\t}\n\n\tmatch := yamlContent[loc[0]:loc[1]]\n\tif strings.Contains(match, \".Values.manager.args\") {\n\t\treturn yamlContent\n\t}\n\n\tindent := yamlContent[loc[2]:loc[3]]\n\titemsBlock := yamlContent[loc[4]:loc[5]]\n\n\titemIndent := indent + \"  \"\n\tlines := strings.Split(itemsBlock, \"\\n\")\n\tvar (\n\t\tmetricsLine    string\n\t\tmetricsIndent  string\n\t\thealthLine     string\n\t\tpreservedLines []string\n\t)\n\n\tfor _, rawLine := range lines {\n\t\tline := strings.TrimRight(rawLine, \"\\r\")\n\t\ttrimmed := strings.TrimSpace(line)\n\t\tif trimmed == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif itemIndent == indent+\"  \" {\n\t\t\tif idx := strings.Index(line, \"-\"); idx > 0 {\n\t\t\t\titemIndent = line[:idx]\n\t\t\t}\n\t\t}\n\n\t\tswitch {\n\t\tcase strings.Contains(trimmed, \"--metrics-bind-address\"):\n\t\t\tmetricsLine = line\n\t\t\tif idx := strings.Index(line, \"-\"); idx > 0 {\n\t\t\t\tmetricsIndent = line[:idx]\n\t\t\t}\n\t\tcase strings.Contains(trimmed, \"--health-probe-bind-address\"):\n\t\t\thealthLine = line\n\t\tcase strings.Contains(trimmed, \"--webhook-cert-path\"),\n\t\t\tstrings.Contains(trimmed, \"--metrics-cert-path\"):\n\t\t\tpreservedLines = append(preservedLines, line)\n\t\tdefault:\n\t\t\t// Remaining args will be handled through values.yaml\n\t\t}\n\t}\n\n\tvar builder strings.Builder\n\tbuilder.WriteString(indent)\n\tbuilder.WriteString(\"args:\\n\")\n\n\tif metricsLine != \"\" {\n\t\tif metricsIndent == \"\" {\n\t\t\tmetricsIndent = itemIndent\n\t\t}\n\t\tbuilder.WriteString(metricsIndent)\n\t\tbuilder.WriteString(\"{{- if .Values.metrics.enable }}\\n\")\n\t\tbuilder.WriteString(metricsLine)\n\t\tbuilder.WriteString(\"\\n\")\n\t\tbuilder.WriteString(metricsIndent)\n\t\tbuilder.WriteString(\"{{- else }}\\n\")\n\t\tbuilder.WriteString(metricsIndent)\n\t\tbuilder.WriteString(\"# Bind to :0 to disable the controller-runtime managed metrics server\\n\")\n\t\tbuilder.WriteString(metricsIndent)\n\t\tbuilder.WriteString(\"- --metrics-bind-address=0\\n\")\n\t\tbuilder.WriteString(metricsIndent)\n\t\tbuilder.WriteString(\"{{- end }}\\n\")\n\t}\n\tif healthLine != \"\" {\n\t\tbuilder.WriteString(healthLine)\n\t\tbuilder.WriteString(\"\\n\")\n\t}\n\n\tbuilder.WriteString(itemIndent)\n\tbuilder.WriteString(\"{{- range .Values.manager.args }}\\n\")\n\tbuilder.WriteString(itemIndent)\n\tbuilder.WriteString(\"- {{ . }}\\n\")\n\tbuilder.WriteString(itemIndent)\n\tbuilder.WriteString(\"{{- end }}\\n\")\n\n\tfor _, line := range preservedLines {\n\t\tbuilder.WriteString(line)\n\t\tbuilder.WriteString(\"\\n\")\n\t}\n\n\tnewBlock := strings.TrimRight(builder.String(), \"\\n\") + \"\\n\"\n\n\treturn yamlContent[:loc[0]] + newBlock + yamlContent[loc[1]:]\n}\n\n// templateImageReference converts hardcoded image references to Helm templates\nfunc (t *HelmTemplater) templateImageReference(yamlContent string) string {\n\tcontainerName := t.getDefaultContainerName(yamlContent)\n\t// Check for both literal container name and templated container name (which may have been\n\t// converted by substituteResourceNamesWithPrefix before this function runs)\n\thasLiteralName := strings.Contains(yamlContent, \"name: \"+containerName)\n\thasTemplatedName := strings.Contains(yamlContent, `name: {{ include \"`) && strings.Contains(yamlContent, `\"manager\"`)\n\tif !hasLiteralName && !hasTemplatedName {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tfor i := 0; i < len(lines); i++ {\n\t\ttrimmed := strings.TrimSpace(lines[i])\n\t\tif !strings.HasPrefix(trimmed, \"image:\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.Contains(lines[i], \".Values.manager.image.repository\") {\n\t\t\treturn yamlContent\n\t\t}\n\n\t\tindentStr, indentLen := leadingWhitespace(lines[i])\n\n\t\tend := i + 1\n\t\tfor ; end < len(lines); end++ {\n\t\t\tnextTrimmed := strings.TrimSpace(lines[end])\n\t\t\tif nextTrimmed == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tlineIndent := len(lines[end]) - len(strings.TrimLeft(lines[end], \" \\t\"))\n\t\t\tif lineIndent <= indentLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// Stop when we reach a sibling key like env:, args:, etc.\n\t\t\tif lineIndent == indentLen+2 && strings.HasSuffix(nextTrimmed, \":\") {\n\t\t\t\tif strings.Contains(nextTrimmed, \"imagePullPolicy\") {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// Remove any existing imagePullPolicy line inside the block\n\t\tblockLines := lines[i+1 : end]\n\t\tfiltered := make([]string, 0, len(blockLines))\n\t\tfor _, line := range blockLines {\n\t\t\tif strings.Contains(strings.TrimSpace(line), \"imagePullPolicy\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfiltered = append(filtered, line)\n\t\t}\n\t\tlines = append(lines[:i+1], append(filtered, lines[end:]...)...)\n\t\tend = i + 1 + len(filtered)\n\n\t\timageLine := indentStr + \"image: \\\"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\\\"\"\n\t\tpullPolicyLine := indentStr + \"imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\"\n\n\t\tremainder := lines[end:]\n\t\tif len(remainder) > 0 && strings.HasPrefix(strings.TrimSpace(remainder[0]), \"imagePullPolicy:\") {\n\t\t\tremainder = remainder[1:]\n\t\t}\n\n\t\tnewLines := append([]string{}, lines[:i]...)\n\t\tnewLines = append(newLines, imageLine, pullPolicyLine)\n\t\tnewLines = append(newLines, remainder...)\n\t\treturn strings.Join(newLines, \"\\n\")\n\t}\n\n\treturn yamlContent\n}\n\nfunc (t *HelmTemplater) templateBasicWithStatement(\n\tyamlContent string,\n\tkey string,\n\tparentKey string,\n\tvaluePath string,\n) string {\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tyamlKey := fmt.Sprintf(\"%s:\", key)\n\n\tvar start, end int\n\tvar indentLen int\n\tif !strings.Contains(yamlContent, yamlKey) {\n\t\t// Find parent block start if the key is missing\n\t\tpKeyParts := strings.Split(parentKey, \".\")\n\t\tpKeyIdx := 0\n\t\tpKeyInit := false\n\t\tcurrIndent := 0\n\t\tfor i := range len(lines) {\n\t\t\t_, lineIndent := leadingWhitespace(lines[i])\n\t\t\tif pKeyInit && lineIndent <= currIndent {\n\t\t\t\treturn yamlContent\n\t\t\t}\n\t\t\tif !strings.HasPrefix(strings.TrimSpace(lines[i]), pKeyParts[pKeyIdx]) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Parent key part found\n\t\t\tpKeyIdx++\n\t\t\tpKeyInit = true\n\t\t\tif pKeyIdx >= len(pKeyParts) {\n\t\t\t\tstart = i + 1\n\t\t\t\tend = start\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t_, indentLen = leadingWhitespace(lines[start])\n\t} else {\n\t\t// Find the existing block\n\t\tfor i := range len(lines) {\n\t\t\tif !strings.HasPrefix(strings.TrimSpace(lines[i]), key) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstart = i\n\t\t\tend = i + 1\n\t\t\ttrimmed := strings.TrimSpace(lines[i])\n\t\t\tif len(trimmed) == len(yamlKey) {\n\t\t\t\t_, indentLenSearch := leadingWhitespace(lines[i])\n\t\t\t\tfor j := end; j < len(lines); j++ {\n\t\t\t\t\t_, indentLenLine := leadingWhitespace(lines[j])\n\t\t\t\t\tif indentLenLine <= indentLenSearch {\n\t\t\t\t\t\tend = j\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t_, indentLen = leadingWhitespace(lines[start])\n\t}\n\n\tindentStr := strings.Repeat(\" \", indentLen)\n\n\tvar builder strings.Builder\n\tbuilder.WriteString(indentStr)\n\tbuilder.WriteString(\"{{- with \")\n\tbuilder.WriteString(valuePath)\n\tbuilder.WriteString(\" }}\\n\")\n\tbuilder.WriteString(indentStr)\n\tbuilder.WriteString(yamlKey)\n\tbuilder.WriteString(\" {{ toYaml . | nindent \")\n\tbuilder.WriteString(strconv.Itoa(indentLen + 4))\n\tbuilder.WriteString(\" }}\\n\")\n\tbuilder.WriteString(indentStr)\n\tbuilder.WriteString(\"{{- end }}\\n\")\n\n\tnewBlock := strings.TrimRight(builder.String(), \"\\n\")\n\n\tnewLines := append([]string{}, lines[:start]...)\n\tnewLines = append(newLines, strings.Split(newBlock, \"\\n\")...)\n\tnewLines = append(newLines, lines[end:]...)\n\treturn strings.Join(newLines, \"\\n\")\n}\n\n// makeWebhookAnnotationsConditional makes only cert-manager annotations conditional, not the entire webhook\nfunc (t *HelmTemplater) makeWebhookAnnotationsConditional(yamlContent string) string {\n\t// Find cert-manager.io/inject-ca-from annotation and make it conditional\n\tif strings.Contains(yamlContent, \"cert-manager.io/inject-ca-from\") {\n\t\t// Replace the cert-manager annotation with conditional wrapper\n\t\tcertManagerPattern := regexp.MustCompile(`(\\s+)cert-manager\\.io/inject-ca-from:\\s*[^\\n]+`)\n\t\tyamlContent = certManagerPattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t\t// Extract the indentation\n\t\t\tindentMatch := regexp.MustCompile(`^(\\s+)`).FindStringSubmatch(match)\n\t\t\tindent := \"\"\n\t\t\tif len(indentMatch) > 1 {\n\t\t\t\tindent = indentMatch[1]\n\t\t\t}\n\n\t\t\t// Extract the annotation line with proper indentation\n\t\t\tannotationLine := strings.TrimSpace(match)\n\n\t\t\treturn fmt.Sprintf(\"%s{{- if .Values.certManager.enable }}\\n%s%s\\n%s{{- end }}\",\n\t\t\t\tindent, indent, annotationLine, indent)\n\t\t})\n\t}\n\n\treturn yamlContent\n}\n\n// makeContainerArgsConditional makes webhook-cert-path and metrics-cert-path args conditional on certManager.enable\nfunc (t *HelmTemplater) makeContainerArgsConditional(yamlContent string) string {\n\t// Make webhook-cert-path arg conditional on certManager.enable\n\tif strings.Contains(yamlContent, \"--webhook-cert-path\") {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\twebhookArgPattern := regexp.MustCompile(`([ \\t]+)-\\s*--webhook-cert-path=[^\\n]*`)\n\t\tyamlContent = webhookArgPattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t\tindentMatch := regexp.MustCompile(`^(\\s+)`).FindStringSubmatch(match)\n\t\t\tindent := \"\"\n\t\t\tif len(indentMatch) > 1 {\n\t\t\t\tindent = indentMatch[1]\n\t\t\t}\n\n\t\t\targLine := strings.TrimSpace(match)\n\t\t\treturn fmt.Sprintf(\"%s{{- if .Values.certManager.enable }}\\n%s%s\\n%s{{- end }}\",\n\t\t\t\tindent, indent, argLine, indent)\n\t\t})\n\t}\n\n\t// Make metrics-cert-path arg conditional on certManager.enable AND metrics.enable\n\tif strings.Contains(yamlContent, \"--metrics-cert-path\") {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\tmetricsArgPattern := regexp.MustCompile(`([ \\t]+)-\\s*--metrics-cert-path=[^\\n]*`)\n\t\tyamlContent = metricsArgPattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t\tindentMatch := regexp.MustCompile(`^(\\s+)`).FindStringSubmatch(match)\n\t\t\tindent := \"\"\n\t\t\tif len(indentMatch) > 1 {\n\t\t\t\tindent = indentMatch[1]\n\t\t\t}\n\n\t\t\targLine := strings.TrimSpace(match)\n\t\t\treturn fmt.Sprintf(\"%s{{- if and .Values.certManager.enable .Values.metrics.enable }}\\n%s%s\\n%s{{- end }}\",\n\t\t\t\tindent, indent, argLine, indent)\n\t\t})\n\t}\n\n\treturn yamlContent\n}\n\nfunc makeYamlContent(match string) string {\n\tlines := strings.Split(match, \"\\n\")\n\tif len(lines) > 0 {\n\t\tindent := \"\"\n\t\tif len(lines[0]) > 0 && lines[0][0] == ' ' {\n\t\t\t// Count leading spaces\n\t\t\tfor _, char := range lines[0] {\n\t\t\t\tif char == ' ' {\n\t\t\t\t\tindent += \" \"\n\t\t\t\t} else {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Reconstruct the block with conditional wrapper\n\t\tvar result strings.Builder\n\t\tresult.WriteString(fmt.Sprintf(\"%s{{- if .Values.certManager.enable }}\\n\", indent))\n\t\tfor _, line := range lines {\n\t\t\tresult.WriteString(line + \"\\n\")\n\t\t}\n\t\tresult.WriteString(fmt.Sprintf(\"%s{{- end }}\", indent))\n\t\treturn result.String()\n\t}\n\treturn match\n}\n\n// makeWebhookVolumesConditional makes webhook volumes conditional on certManager.enable\nfunc (t *HelmTemplater) makeWebhookVolumesConditional(yamlContent string) string {\n\t// Make webhook volumes conditional on certManager.enable\n\tif strings.Contains(yamlContent, \"webhook-certs\") && strings.Contains(yamlContent, \"secretName: webhook-server-cert\") {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\tvolumePattern := regexp.MustCompile(`([ \\t]+)-\\s*name:\\s*webhook-certs[\\s\\S]*?secretName:\\s*webhook-server-cert`)\n\t\tyamlContent = volumePattern.ReplaceAllStringFunc(yamlContent, makeYamlContent)\n\t}\n\n\treturn yamlContent\n}\n\n// makeWebhookVolumeMountsConditional makes webhook volumeMounts conditional on certManager.enable\nfunc (t *HelmTemplater) makeWebhookVolumeMountsConditional(yamlContent string) string {\n\t// Make webhook volumeMounts conditional on certManager.enable\n\twebhookCertsPath := \"/tmp/k8s-webhook-server/serving-certs\"\n\tif strings.Contains(yamlContent, \"webhook-certs\") && strings.Contains(yamlContent, webhookCertsPath) {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\tmountPattern := regexp.MustCompile(\n\t\t\t`([ \\t]+)-\\s*mountPath:\\s*/tmp/k8s-webhook-server/serving-certs[\\s\\S]*?readOnly:\\s*true`)\n\t\tyamlContent = mountPattern.ReplaceAllStringFunc(yamlContent, makeYamlContent)\n\t}\n\n\treturn yamlContent\n}\n\n// makeMetricsVolumesConditional makes metrics volumes conditional on certManager.enable AND metrics.enable\nfunc (t *HelmTemplater) makeMetricsVolumesConditional(yamlContent string) string {\n\t// Make metrics volumes conditional on certManager.enable AND metrics.enable\n\tif strings.Contains(yamlContent, \"metrics-certs\") && strings.Contains(yamlContent, \"secretName: metrics-server-cert\") {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\tvolumePattern := regexp.MustCompile(`([ \\t]+)-\\s*name:\\s*metrics-certs[\\s\\S]*?secretName:\\s*metrics-server-cert`)\n\t\tyamlContent = volumePattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t\tlines := strings.Split(match, \"\\n\")\n\t\t\tif len(lines) > 0 {\n\t\t\t\tindent := \"\"\n\t\t\t\tif len(lines[0]) > 0 && lines[0][0] == ' ' {\n\t\t\t\t\t// Count leading spaces\n\t\t\t\t\tfor _, char := range lines[0] {\n\t\t\t\t\t\tif char == ' ' {\n\t\t\t\t\t\t\tindent += \" \"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Reconstruct the block with conditional wrapper\n\t\t\t\tvar result strings.Builder\n\t\t\t\tresult.WriteString(fmt.Sprintf(\"%s{{- if and .Values.certManager.enable .Values.metrics.enable }}\\n\", indent))\n\t\t\t\tfor _, line := range lines {\n\t\t\t\t\tresult.WriteString(line + \"\\n\")\n\t\t\t\t}\n\t\t\t\tresult.WriteString(fmt.Sprintf(\"%s{{- end }}\", indent))\n\t\t\t\treturn result.String()\n\t\t\t}\n\t\t\treturn match\n\t\t})\n\t}\n\n\treturn yamlContent\n}\n\n// injectCommonLabels adds a Helm template snippet to append user-provided common labels\n// (.Values.commonLabels) to every metadata.labels block while preserving indentation.\n// It avoids duplicate insertion by checking for an existing snippet nearby.\n// no common labels injection; labels come from kustomize manifests\n\n// makeMetricsVolumeMountsConditional makes metrics volumeMounts conditional on certManager.enable AND metrics.enable\nfunc (t *HelmTemplater) makeMetricsVolumeMountsConditional(yamlContent string) string {\n\t// Make metrics volumeMounts conditional on certManager.enable AND metrics.enable\n\tmetricsCertsPath := \"/tmp/k8s-metrics-server/metrics-certs\"\n\tif strings.Contains(yamlContent, \"metrics-certs\") && strings.Contains(yamlContent, metricsCertsPath) {\n\t\t// Match only spaces/tabs for indent to avoid consuming the newline\n\t\tmountPattern := regexp.MustCompile(\n\t\t\t`([ \\t]+)-\\s*mountPath:\\s*/tmp/k8s-metrics-server/metrics-certs[\\s\\S]*?readOnly:\\s*true`)\n\t\tyamlContent = mountPattern.ReplaceAllStringFunc(yamlContent, func(match string) string {\n\t\t\tlines := strings.Split(match, \"\\n\")\n\t\t\tif len(lines) > 0 {\n\t\t\t\tindent := \"\"\n\t\t\t\tif len(lines[0]) > 0 && lines[0][0] == ' ' {\n\t\t\t\t\t// Count leading spaces\n\t\t\t\t\tfor _, char := range lines[0] {\n\t\t\t\t\t\tif char == ' ' {\n\t\t\t\t\t\t\tindent += \" \"\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Reconstruct the block with conditional wrapper\n\t\t\t\tvar result strings.Builder\n\t\t\t\tresult.WriteString(fmt.Sprintf(\"%s{{- if and .Values.certManager.enable .Values.metrics.enable }}\\n\", indent))\n\t\t\t\tfor _, line := range lines {\n\t\t\t\t\tresult.WriteString(line + \"\\n\")\n\t\t\t\t}\n\t\t\t\tresult.WriteString(fmt.Sprintf(\"%s{{- end }}\", indent))\n\t\t\t\treturn result.String()\n\t\t\t}\n\t\t\treturn match\n\t\t})\n\t}\n\n\treturn yamlContent\n}\n\n// injectCRDResourcePolicyAnnotation adds the helm.sh/resource-policy: keep annotation\n// to CRDs conditionally based on .Values.crd.keep. This prevents CRDs from being deleted\n// on helm uninstall when crd.keep is true in values.yaml.\nfunc (t *HelmTemplater) injectCRDResourcePolicyAnnotation(yamlContent string) string {\n\t// Check if metadata section exists\n\tif !strings.Contains(yamlContent, \"metadata:\") {\n\t\treturn yamlContent\n\t}\n\n\tlines := strings.Split(yamlContent, \"\\n\")\n\n\t// Check if annotations: already exists\n\tif strings.Contains(yamlContent, \"annotations:\") {\n\t\t// Find the annotations: line and determine its indentation\n\t\tfor i, line := range lines {\n\t\t\ttrimmed := strings.TrimSpace(line)\n\t\t\tif trimmed == \"annotations:\" || strings.HasPrefix(trimmed, \"annotations:\") {\n\t\t\t\tannotationsIndent, _ := leadingWhitespace(line)\n\t\t\t\t// Annotation values need one more level of indentation (2 spaces for sigs.k8s.io/yaml)\n\t\t\t\tvalueIndent := annotationsIndent + \"  \"\n\n\t\t\t\t// Build the conditional annotation block\n\t\t\t\tresourcePolicyBlock := fmt.Sprintf(\n\t\t\t\t\t\"%s{{- if .Values.crd.keep }}\\n%s\\\"helm.sh/resource-policy\\\": keep\\n%s{{- end }}\",\n\t\t\t\t\tvalueIndent, valueIndent, valueIndent)\n\n\t\t\t\t// Insert after the annotations: line\n\t\t\t\tresult := make([]string, 0, len(lines)+3)\n\t\t\t\tresult = append(result, lines[:i+1]...)\n\t\t\t\tresult = append(result, resourcePolicyBlock)\n\t\t\t\tresult = append(result, lines[i+1:]...)\n\t\t\t\treturn strings.Join(result, \"\\n\")\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// No annotations section exists, need to add it after metadata:\n\t\tfor i, line := range lines {\n\t\t\ttrimmed := strings.TrimSpace(line)\n\t\t\tif trimmed == \"metadata:\" || strings.HasPrefix(trimmed, \"metadata:\") {\n\t\t\t\tmetadataIndent, _ := leadingWhitespace(line)\n\t\t\t\t// Fields under metadata need one more level of indentation (2 spaces for sigs.k8s.io/yaml)\n\t\t\t\tfieldIndent := metadataIndent + \"  \"\n\t\t\t\t// Annotation values need two more levels (4 spaces total)\n\t\t\t\tvalueIndent := metadataIndent + \"    \"\n\n\t\t\t\t// Build annotations section with conditional resource-policy\n\t\t\t\tannotationsSection := fmt.Sprintf(\n\t\t\t\t\t\"%sannotations:\\n%s{{- if .Values.crd.keep }}\\n%s\\\"helm.sh/resource-policy\\\": keep\\n%s{{- end }}\",\n\t\t\t\t\tfieldIndent, valueIndent, valueIndent, valueIndent)\n\n\t\t\t\t// Insert after the metadata: line\n\t\t\t\tresult := make([]string, 0, len(lines)+4)\n\t\t\t\tresult = append(result, lines[:i+1]...)\n\t\t\t\tresult = append(result, annotationsSection)\n\t\t\t\tresult = append(result, lines[i+1:]...)\n\t\t\t\treturn strings.Join(result, \"\\n\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn yamlContent\n}\n\n// addConditionalWrappers adds conditional Helm logic based on resource type\nfunc (t *HelmTemplater) addConditionalWrappers(yamlContent string, resource *unstructured.Unstructured) string {\n\tkind := resource.GetKind()\n\tapiVersion := resource.GetAPIVersion()\n\tname := resource.GetName()\n\n\tswitch {\n\tcase kind == kindNamespace:\n\t\treturn \"\"\n\tcase kind == \"CustomResourceDefinition\":\n\t\t// CRDs need resource-policy annotation for helm uninstall protection\n\t\tyamlContent = t.injectCRDResourcePolicyAnnotation(yamlContent)\n\t\t// CRDs need crd.enable condition\n\t\treturn fmt.Sprintf(\"{{- if .Values.crd.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\tcase kind == kindCertificate && apiVersion == apiVersionCertManager:\n\t\t// Handle different certificate types\n\t\tif strings.Contains(name, \"metrics-cert\") || strings.Contains(name, \"metrics\") {\n\t\t\t// Metrics certificates need both certManager and metrics enabled\n\t\t\treturn fmt.Sprintf(\"{{- if and .Values.certManager.enable .Values.metrics.enable }}\\n%s{{- end }}\\n\",\n\t\t\t\tyamlContent)\n\t\t}\n\t\t// Other certificates (webhook serving certs) only need certManager enabled\n\t\treturn fmt.Sprintf(\"{{- if .Values.certManager.enable }}\\n%s{{- end }}\", yamlContent)\n\tcase kind == kindIssuer && apiVersion == apiVersionCertManager:\n\t\t// All cert-manager issuers need certManager enabled\n\t\treturn fmt.Sprintf(\"{{- if .Values.certManager.enable }}\\n%s{{- end }}\", yamlContent)\n\tcase kind == kindServiceMonitor && apiVersion == apiVersionMonitoring:\n\t\t// ServiceMonitors need prometheus enabled\n\t\treturn fmt.Sprintf(\"{{- if .Values.prometheus.enable }}\\n%s{{- end }}\", yamlContent)\n\tcase kind == kindServiceAccount || kind == kindRole || kind == kindClusterRole ||\n\t\tkind == kindRoleBinding || kind == kindClusterRoleBinding:\n\t\t// Distinguish between essential RBAC and helper RBAC\n\t\tif strings.Contains(name, \"admin-role\") || strings.Contains(name, \"editor-role\") ||\n\t\t\tstrings.Contains(name, \"viewer-role\") {\n\t\t\t// Helper RBAC roles (admin/editor/viewer) - convenience roles for CRD management\n\t\t\treturn fmt.Sprintf(\"{{- if .Values.rbacHelpers.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\t\t}\n\t\tif strings.Contains(name, \"metrics\") {\n\t\t\t// Metrics RBAC depends on metrics being enabled\n\t\t\treturn fmt.Sprintf(\"{{- if .Values.metrics.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\t\t}\n\t\t// Essential RBAC (controller-manager, leader-election, manager roles) - always enabled\n\t\t// These are required for the controller to function properly\n\t\treturn yamlContent\n\tcase kind == kindValidatingWebhook || kind == kindMutatingWebhook:\n\t\t// Webhook configurations should be conditional on webhook.enable\n\t\tyamlContent = t.makeWebhookAnnotationsConditional(yamlContent)\n\t\treturn fmt.Sprintf(\"{{- if .Values.webhook.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\tcase kind == kindService:\n\t\t// Services need conditional logic based on their purpose\n\t\tif strings.Contains(name, \"metrics\") {\n\t\t\t// Metrics services need metrics enabled\n\t\t\treturn fmt.Sprintf(\"{{- if .Values.metrics.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\t\t}\n\t\tif strings.Contains(name, \"webhook\") {\n\t\t\t// Webhook services need webhook enabled\n\t\t\treturn fmt.Sprintf(\"{{- if .Values.webhook.enable }}\\n%s{{- end }}\\n\", yamlContent)\n\t\t}\n\t\t// Other services don't need conditionals\n\t\treturn yamlContent\n\tdefault:\n\t\t// No conditional wrapper needed for other resources (Deployment, Namespace)\n\t\treturn yamlContent\n\t}\n}\n\n// collapseBlankLineAfterIf removes a single empty line that may appear\n// immediately after a Helm if directive line, e.g. `{{- if ... }}`.\n// This keeps templates compact and matches expected formatting in tests.\nfunc (t *HelmTemplater) collapseBlankLineAfterIf(yamlContent string) string {\n\tlines := strings.Split(yamlContent, \"\\n\")\n\tif len(lines) == 0 {\n\t\treturn yamlContent\n\t}\n\tout := make([]string, 0, len(lines))\n\tfor i := 0; i < len(lines); i++ {\n\t\tline := lines[i]\n\t\t// If current line is an if, and next line is blank, skip the blank\n\t\tif strings.Contains(line, \"{{- if \") {\n\t\t\tout = append(out, line)\n\t\t\tif i+1 < len(lines) && strings.TrimSpace(lines[i+1]) == \"\" {\n\t\t\t\ti++ // skip one blank line after if\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// If current line is blank, and next line is an end, skip the blank\n\t\tif strings.TrimSpace(line) == \"\" && i+1 < len(lines) && strings.Contains(lines[i+1], \"{{- end }}\") {\n\t\t\tcontinue\n\t\t}\n\t\tout = append(out, line)\n\t}\n\treturn strings.Join(out, \"\\n\")\n}\n\n// templatePorts replaces hardcoded port values with Helm template references\n// This makes ports configurable via values.yaml under webhook.port and metrics.port\nfunc (t *HelmTemplater) templatePorts(yamlContent string, resource *unstructured.Unstructured) string {\n\tresourceName := resource.GetName()\n\n\t// Determine if this is a webhook-related resource\n\tisWebhook := strings.Contains(resourceName, \"webhook\")\n\n\t// Determine if this is a metrics-related resource\n\tisMetrics := strings.Contains(resourceName, \"metrics\")\n\n\t// For Deployments, check for webhook ports in the content\n\tif resource.GetKind() == kindDeployment {\n\t\t// Check if this deployment has webhook-server ports\n\t\tif strings.Contains(yamlContent, \"webhook-server\") || strings.Contains(yamlContent, \"name: webhook\") {\n\t\t\tisWebhook = true\n\t\t}\n\t}\n\n\t// Template webhook ports (9443 by default)\n\tif isWebhook {\n\t\t// Replace containerPort: 9443 (or any value) for webhook-server with template\n\t\tif strings.Contains(yamlContent, \"webhook-server\") {\n\t\t\tyamlContent = regexp.MustCompile(`(?m)(\\s*- )?containerPort:\\s*\\d+(\\s*\\n\\s*name:\\s*webhook-server)`).\n\t\t\t\tReplaceAllString(yamlContent, \"${1}containerPort: {{ .Values.webhook.port }}${2}\")\n\t\t}\n\n\t\t// Replace targetPort: 9443 with webhook.port template\n\t\tyamlContent = regexp.MustCompile(`(\\s*)targetPort:\\s*9443`).\n\t\t\tReplaceAllString(yamlContent, \"${1}targetPort: {{ .Values.webhook.port }}\")\n\t}\n\n\t// Template metrics ports (8443 by default)\n\tif isMetrics {\n\t\t// Replace port: 8443 with metrics.port template\n\t\tyamlContent = regexp.MustCompile(`(\\s*)port:\\s*8443`).\n\t\t\tReplaceAllString(yamlContent, \"${1}port: {{ .Values.metrics.port }}\")\n\n\t\t// Replace targetPort: 8443 with metrics.port template\n\t\tyamlContent = regexp.MustCompile(`(\\s*)targetPort:\\s*8443`).\n\t\t\tReplaceAllString(yamlContent, \"${1}targetPort: {{ .Values.metrics.port }}\")\n\t}\n\n\t// Template port-related arguments in Deployment\n\tif resource.GetKind() == kindDeployment {\n\t\t// Replace --metrics-bind-address=:8443 with templated version\n\t\tyamlContent = regexp.MustCompile(`--metrics-bind-address=:[0-9]+`).\n\t\t\tReplaceAllString(yamlContent, \"--metrics-bind-address=:{{ .Values.metrics.port }}\")\n\n\t\t// Replace --webhook-port=9443 with templated version (if present)\n\t\tyamlContent = regexp.MustCompile(`--webhook-port=[0-9]+`).\n\t\t\tReplaceAllString(yamlContent, \"--webhook-port={{ .Values.webhook.port }}\")\n\t}\n\n\treturn yamlContent\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/helm_templater_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nconst (\n\t// Test expectation constants for test-project.resourceName templates\n\texpectedIssuerName = `name: {{ include \"test-project.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}`\n)\n\nvar _ = Describe(\"HelmTemplater\", func() {\n\tvar templater *HelmTemplater\n\n\tBeforeEach(func() {\n\t\ttemplater = &HelmTemplater{\n\t\t\tdetectedPrefix:   \"test-project\",\n\t\t\tchartName:        \"test-project\",\n\t\t\tmanagerNamespace: \"test-project-system\",\n\t\t}\n\t})\n\n\t// No global labels injection is performed by v2-alpha\n\n\tContext(\"basic template processing\", func() {\n\t\tIt(\"should replace kustomize managed-by labels with Helm equivalents\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  template:\n    metadata:\n      labels:\n        control-plane: controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\n\t\t\t// Should replace kustomize managed-by with Helm template\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/managed-by: {{ .Release.Service }}\"))\n\t\t\t// Should replace app.kubernetes.io/name with chart name template\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"control-plane: controller-manager\"))\n\n\t\t\t// Should substitute namespace\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\n\t\t\t// Should NOT add extra Helm metadata injection\n\t\t\tExpect(result).NotTo(ContainSubstring(`{{- include \"chart.labels\"`))\n\t\t\tExpect(result).NotTo(ContainSubstring(`{{- include \"chart.annotations\"`))\n\t\t})\n\n\t\tIt(\"should handle cert-manager annotations with proper indentation\", func() {\n\t\t\tresource := &unstructured.Unstructured{}\n\t\t\tresource.SetAPIVersion(\"admissionregistration.k8s.io/v1\")\n\t\t\tresource.SetKind(\"ValidatingWebhookConfiguration\")\n\t\t\tresource.SetName(\"test-project-validating-webhook-configuration\")\n\n\t\t\tcontent := `apiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: test-project-system/test-project-serving-cert\n  name: test-project-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, resource)\n\n\t\t\t// Should have proper conditional formatting without extra spaces\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.certManager.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"cert-manager.io/inject-ca-from:\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\n\t\t\t// Should NOT have extra blank lines or improper indentation\n\t\t\tExpect(result).NotTo(ContainSubstring(\"{{- if .Values.certManager.enable }}\\n\\n\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"cert-manager.io/inject-ca-from:\\n\\n\"))\n\t\t})\n\n\t\tIt(\"should template deployment spec.replicas from .Values.manager.replicas\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n  template:\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n`\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\t\t\tExpect(result).To(ContainSubstring(\"replicas: {{ .Values.manager.replicas }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"replicas: 1\"))\n\t\t})\n\n\t\tIt(\"should handle container args with proper indentation\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs/tls.crt\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs/tls.crt\n        - --leader-elect\n        env:\n        - name: BUSYBOX_IMAGE\n          value: busybox:1.36.1\n        image: controller:latest\n        imagePullPolicy: IfNotPresent\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          readOnlyRootFilesystem: true\n          capabilities:\n            drop:\n            - ALL\n        name: manager\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: test-project-controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.metrics.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- --metrics-bind-address=:{{ .Values.metrics.port }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- --metrics-bind-address=0\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- --health-probe-bind-address=:8081\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- range .Values.manager.args }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"BUSYBOX_IMAGE\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"MEMCACHED_IMAGE\"))\n\t\t\tExpect(result).To(ContainSubstring(\"image: \" +\n\t\t\t\t\"\\\"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\\\"\"))\n\t\t\tExpect(result).To(ContainSubstring(\"imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"controller:latest\"))\n\t\t})\n\n\t\tIt(\"should handle volume mounts with proper indentation\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      containers:\n      - volumeMounts:\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n        - mountPath: /tmp/k8s-metrics-server/metrics-certs\n          name: metrics-certs\n          readOnly: true`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\n\t\t\t// Should have conditional blocks for webhook certs\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.certManager.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"mountPath: /tmp/k8s-webhook-server/serving-certs\"))\n\n\t\t\t// Should have conditional blocks for metrics certs\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if and .Values.certManager.enable .Values.metrics.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"mountPath: /tmp/k8s-metrics-server/metrics-certs\"))\n\t\t})\n\n\t\tIt(\"should handle namespace substitution correctly\", func() {\n\t\t\tserviceResource := &unstructured.Unstructured{}\n\t\t\tserviceResource.SetAPIVersion(\"v1\")\n\t\t\tserviceResource.SetKind(\"Service\")\n\t\t\tserviceResource.SetName(\"test-project-webhook-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-webhook-service\n  namespace: test-project-system\nspec:\n  type: ClusterIP`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, serviceResource)\n\n\t\t\t// Should substitute namespace with Helm template\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace: test-project-system\"))\n\t\t})\n\n\t\tIt(\"should preserve annotations without modification\", func() {\n\t\t\twebhookResource := &unstructured.Unstructured{}\n\t\t\twebhookResource.SetAPIVersion(\"admissionregistration.k8s.io/v1\")\n\t\t\twebhookResource.SetKind(\"ValidatingWebhookConfiguration\")\n\t\t\twebhookResource.SetName(\"test-project-validating-webhook-configuration\")\n\n\t\t\tcontent := `apiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: test-system/test-serving-cert\n  name: test-project-validating-webhook-configuration`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, webhookResource)\n\n\t\t\t// Should preserve existing kustomize annotations as-is\n\t\t\tExpect(result).To(ContainSubstring(\"cert-manager.io/inject-ca-from\"))\n\n\t\t\t// Should NOT add extra Helm metadata injection\n\t\t\tExpect(result).NotTo(ContainSubstring(`{{- include \"chart.labels\"`))\n\t\t\tExpect(result).NotTo(ContainSubstring(`{{- include \"chart.annotations\"`))\n\t\t})\n\n\t\tIt(\"should template imagePullSecrets\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      imagePullSecrets:\n      - name: test-secret\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs/tls.crt\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs/tls.crt\n        - --leader-elect`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\n\t\t\tExpect(result).To(ContainSubstring(\"imagePullSecrets:\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"test-secret\"))\n\t\t})\n\n\t\tIt(\"should template empty imagePullSecrets\", func() {\n\t\t\tdeploymentResource := &unstructured.Unstructured{}\n\t\t\tdeploymentResource.SetAPIVersion(\"apps/v1\")\n\t\t\tdeploymentResource.SetKind(\"Deployment\")\n\t\t\tdeploymentResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nspec:\n  template:\n    spec:\n      imagePullSecrets: []\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs/tls.crt\n        - --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs/tls.crt\n        - --leader-elect`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deploymentResource)\n\n\t\t\tExpect(result).To(ContainSubstring(\"imagePullSecrets:\"))\n\t\t})\n\t})\n\n\tContext(\"conditional wrapping\", func() {\n\t\tIt(\"should add metrics conditional for ServiceMonitor resources\", func() {\n\t\t\tserviceMonitorResource := &unstructured.Unstructured{}\n\t\t\tserviceMonitorResource.SetAPIVersion(\"monitoring.coreos.com/v1\")\n\t\t\tserviceMonitorResource.SetKind(\"ServiceMonitor\")\n\t\t\tserviceMonitorResource.SetName(\"test-project-controller-manager-metrics-monitor\")\n\n\t\t\tcontent := `apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: test-project-controller-manager-metrics-monitor`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, serviceMonitorResource)\n\n\t\t\t// Should be wrapped with prometheus enable conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.prometheus.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add metrics conditional for metrics services\", func() {\n\t\t\tserviceResource := &unstructured.Unstructured{}\n\t\t\tserviceResource.SetAPIVersion(\"v1\")\n\t\t\tserviceResource.SetKind(\"Service\")\n\t\t\tserviceResource.SetName(\"test-project-controller-manager-metrics-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-controller-manager-metrics-service`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, serviceResource)\n\n\t\t\t// Should be wrapped with metrics enable conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.metrics.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add cert-manager conditional for Certificate resources\", func() {\n\t\t\tcertResource := &unstructured.Unstructured{}\n\t\t\tcertResource.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tcertResource.SetKind(\"Certificate\")\n\t\t\tcertResource.SetName(\"test-project-serving-cert\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: test-project-serving-cert`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, certResource)\n\n\t\t\t// Should be wrapped with certManager enable conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.certManager.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add combined conditionals for metrics certificates\", func() {\n\t\t\tcertResource := &unstructured.Unstructured{}\n\t\t\tcertResource.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tcertResource.SetKind(\"Certificate\")\n\t\t\tcertResource.SetName(\"test-project-metrics-certs\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: test-project-metrics-certs`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, certResource)\n\n\t\t\t// Should be wrapped with both metrics and certManager conditionals\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if and .Values.certManager.enable .Values.metrics.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should NOT add conditionals to essential resources\", func() {\n\t\t\t// Test essential RBAC\n\t\t\tclusterRoleResource := &unstructured.Unstructured{}\n\t\t\tclusterRoleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tclusterRoleResource.SetKind(\"ClusterRole\")\n\t\t\tclusterRoleResource.SetName(\"test-project-manager-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, clusterRoleResource)\n\n\t\t\t// Should NOT wrap essential RBAC with conditionals\n\t\t\tExpect(result).NotTo(ContainSubstring(\"{{- if .Values\"))\n\t\t})\n\n\t\tIt(\"should add webhook conditional for webhook services\", func() {\n\t\t\tserviceResource := &unstructured.Unstructured{}\n\t\t\tserviceResource.SetAPIVersion(\"v1\")\n\t\t\tserviceResource.SetKind(\"Service\")\n\t\t\tserviceResource.SetName(\"test-project-webhook-service\")\n\n\t\t\twebhookContent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-webhook-service`\n\n\t\t\twebhookResult := templater.ApplyHelmSubstitutions(webhookContent, serviceResource)\n\n\t\t\t// Should wrap webhook service with webhook.enable conditional\n\t\t\tExpect(webhookResult).To(ContainSubstring(\"{{- if .Values.webhook.enable }}\"))\n\t\t\tExpect(webhookResult).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add webhook conditional for webhook configurations\", func() {\n\t\t\tmutatingWebhookResource := &unstructured.Unstructured{}\n\t\t\tmutatingWebhookResource.SetAPIVersion(\"admissionregistration.k8s.io/v1\")\n\t\t\tmutatingWebhookResource.SetKind(\"MutatingWebhookConfiguration\")\n\t\t\tmutatingWebhookResource.SetName(\"test-project-mutating-webhook-configuration\")\n\n\t\t\tcontent := `apiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: test-project-mutating-webhook-configuration`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, mutatingWebhookResource)\n\n\t\t\t// Webhook configurations should be conditional on webhook.enable\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.webhook.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add webhook conditional for validating webhook configurations\", func() {\n\t\t\tvalidatingWebhookResource := &unstructured.Unstructured{}\n\t\t\tvalidatingWebhookResource.SetAPIVersion(\"admissionregistration.k8s.io/v1\")\n\t\t\tvalidatingWebhookResource.SetKind(\"ValidatingWebhookConfiguration\")\n\t\t\tvalidatingWebhookResource.SetName(\"test-project-validating-webhook-configuration\")\n\n\t\t\tcontent := `apiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: test-project-system/test-project-serving-cert\n  name: test-project-validating-webhook-configuration`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, validatingWebhookResource)\n\n\t\t\t// Webhook configurations should be wrapped with webhook.enable\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.webhook.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t\t// Cert-manager annotation should still be conditional on certManager.enable\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.certManager.enable }}\"))\n\t\t})\n\n\t\tIt(\"should add crd.enable conditional and resource-policy annotation for CRDs\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"guestbooks.example.com\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: guestbooks.example.com\nspec:\n  group: example.com`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// Should be wrapped with crd.enable conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.crd.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t\t// Should have resource-policy annotation for helm uninstall protection\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.crd.keep }}\"))\n\t\t\tExpect(result).To(ContainSubstring(`\"helm.sh/resource-policy\": keep`))\n\t\t\t// Injected annotations should use 2-space indentation matching sigs.k8s.io/yaml output.\n\t\t\t// annotations: at 2-space indent, values at 4-space indent.\n\t\t\texpectedAnnotations := \"  annotations:\\n\" +\n\t\t\t\t\"    {{- if .Values.crd.keep }}\\n\" +\n\t\t\t\t\"    \\\"helm.sh/resource-policy\\\": keep\\n\" +\n\t\t\t\t\"    {{- end }}\"\n\t\t\tExpect(result).To(ContainSubstring(expectedAnnotations))\n\t\t})\n\n\t\tIt(\"should add resource-policy annotation to CRDs that already have annotations\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"configs.example.com\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.17.0\n  name: configs.example.com\nspec:\n  group: example.com`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// Should be wrapped with crd.enable conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.crd.enable }}\"))\n\t\t\t// Should have resource-policy annotation\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.crd.keep }}\"))\n\t\t\tExpect(result).To(ContainSubstring(`\"helm.sh/resource-policy\": keep`))\n\t\t\t// Should preserve existing annotation\n\t\t\tExpect(result).To(ContainSubstring(\"controller-gen.kubebuilder.io/version\"))\n\t\t\t// Injected annotation should be at same indent as existing annotations (4 spaces)\n\t\t\texpectedAnnotations := \"  annotations:\\n\" +\n\t\t\t\t\"    {{- if .Values.crd.keep }}\\n\" +\n\t\t\t\t\"    \\\"helm.sh/resource-policy\\\": keep\\n\" +\n\t\t\t\t\"    {{- end }}\\n\" +\n\t\t\t\t\"    controller-gen.kubebuilder.io/version\"\n\t\t\tExpect(result).To(ContainSubstring(expectedAnnotations))\n\t\t})\n\t})\n\n\tContext(\"helper RBAC wrapping\", func() {\n\t\tIt(\"should add rbacHelpers conditional for helper RBAC roles\", func() {\n\t\t\tclusterRoleResource := &unstructured.Unstructured{}\n\t\t\tclusterRoleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tclusterRoleResource.SetKind(\"ClusterRole\")\n\t\t\tclusterRoleResource.SetName(\"test-project-memcached-editor-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-memcached-editor-role`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, clusterRoleResource)\n\n\t\t\t// Should be wrapped with rbacHelpers conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.rbacHelpers.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\n\t\tIt(\"should add rbacHelpers conditional for helper ClusterRoleBindings\", func() {\n\t\t\tbindingResource := &unstructured.Unstructured{}\n\t\t\tbindingResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tbindingResource.SetKind(\"ClusterRoleBinding\")\n\t\t\tbindingResource.SetName(\"test-project-memcached-viewer-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: test-project-memcached-viewer-rolebinding`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, bindingResource)\n\n\t\t\t// Should be wrapped with rbacHelpers conditional\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.rbacHelpers.enable }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- end }}\"))\n\t\t})\n\t})\n\n\tContext(\"chart.fullname templating\", func() {\n\t\tIt(\"should template resource names with test-project.resourceName for proper truncation\", func() {\n\t\t\tserviceAccountResource := &unstructured.Unstructured{}\n\t\t\tserviceAccountResource.SetAPIVersion(\"v1\")\n\t\t\tserviceAccountResource.SetKind(\"ServiceAccount\")\n\t\t\tserviceAccountResource.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, serviceAccountResource)\n\n\t\t\t// Should template with test-project.resourceName which handles 63-char truncation\n\t\t\texpected := `name: {{ include \"test-project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}`\n\t\t\tExpect(result).To(ContainSubstring(expected))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-controller-manager\"))\n\t\t})\n\t\tIt(\"should template ServiceMonitor name with test-project.resourceName for proper truncation\", func() {\n\t\t\tserviceMonitorResource := &unstructured.Unstructured{}\n\t\t\tserviceMonitorResource.SetAPIVersion(\"monitoring.coreos.com/v1\")\n\t\t\tserviceMonitorResource.SetKind(\"ServiceMonitor\")\n\t\t\tserviceMonitorResource.SetName(\"test-project-controller-manager-metrics-monitor\")\n\n\t\t\tcontent := `apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: test-project-controller-manager-metrics-monitor`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, serviceMonitorResource)\n\n\t\t\t// Should template with test-project.resourceName which handles 63-char truncation\n\t\t\texpected := `name: {{ include \"test-project.resourceName\" ` +\n\t\t\t\t`(dict \"suffix\" \"controller-manager-metrics-monitor\" \"context\" $) }}`\n\t\t\tExpect(result).To(ContainSubstring(expected))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-controller-manager-metrics-monitor\"))\n\t\t})\n\t})\n\n\tContext(\"app.kubernetes.io/name label templating\", func() {\n\t\tIt(\"should template app.kubernetes.io/name for Deployment\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project\n    control-plane: controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for Service\", func() {\n\t\t\tservice := &unstructured.Unstructured{}\n\t\t\tservice.SetAPIVersion(\"v1\")\n\t\t\tservice.SetKind(\"Service\")\n\t\t\tservice.SetName(\"test-project-webhook-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, service)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for ServiceAccount\", func() {\n\t\t\tsa := &unstructured.Unstructured{}\n\t\t\tsa.SetAPIVersion(\"v1\")\n\t\t\tsa.SetKind(\"ServiceAccount\")\n\t\t\tsa.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, sa)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for ClusterRole\", func() {\n\t\t\tclusterRole := &unstructured.Unstructured{}\n\t\t\tclusterRole.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tclusterRole.SetKind(\"ClusterRole\")\n\t\t\tclusterRole.SetName(\"test-project-manager-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, clusterRole)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for Role\", func() {\n\t\t\trole := &unstructured.Unstructured{}\n\t\t\trole.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\trole.SetKind(\"Role\")\n\t\t\trole.SetName(\"test-project-leader-election-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, role)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for RoleBinding\", func() {\n\t\t\trb := &unstructured.Unstructured{}\n\t\t\trb.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\trb.SetKind(\"RoleBinding\")\n\t\t\trb.SetName(\"test-project-leader-election-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, rb)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for ClusterRoleBinding\", func() {\n\t\t\tcrb := &unstructured.Unstructured{}\n\t\t\tcrb.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tcrb.SetKind(\"ClusterRoleBinding\")\n\t\t\tcrb.SetName(\"test-project-manager-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crb)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for Certificate\", func() {\n\t\t\tcert := &unstructured.Unstructured{}\n\t\t\tcert.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tcert.SetKind(\"Certificate\")\n\t\t\tcert.SetName(\"test-project-serving-cert\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, cert)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template app.kubernetes.io/name for Issuer\", func() {\n\t\t\tissuer := &unstructured.Unstructured{}\n\t\t\tissuer.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tissuer.SetKind(\"Issuer\")\n\t\t\tissuer.SetName(\"test-project-selfsigned-issuer\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, issuer)\n\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should handle label already templated without breaking\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: {{ include \"test-project.name\" . }}\n    control-plane: controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// Should keep the template as-is\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).ToNot(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t})\n\n\t\tIt(\"should template multiple occurrences in same resource\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/name: test-project\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: test-project\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: test-project`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// All three should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: test-project\"))\n\t\t\t// Count occurrences - should be 3\n\t\t\tcount := strings.Count(result, \"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\")\n\t\t\tExpect(count).To(Equal(3))\n\t\t})\n\t})\n\n\tContext(\"existing Go template syntax escaping\", func() {\n\t\tIt(\"should escape existing Go template syntax in CRD samples\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"changetransferpolicies.promoter.argoproj.io\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: changetransferpolicies.promoter.argoproj.io\nspec:\n  names:\n    kind: ChangeTransferPolicy\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            properties:\n              pullRequest:\n                properties:\n                  template:\n                    properties:\n                      description:\n                        default: \"Promoting {{ .ChangeTransferPolicy.Spec.ActiveBranch }}\"\n                        type: string\n                      title:\n                        default: \"Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}\"\n                        type: string`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// Existing {{ }} should be escaped to {{ \"{{ ... }}\" }}\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .ChangeTransferPolicy.Spec.ActiveBranch }}\" }}`),\n\t\t\t\t\"existing template syntax should be escaped\")\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}\" }}`),\n\t\t\t\t\"function calls in templates should be escaped\")\n\n\t\t\t// Should NOT have unescaped Go template syntax (which would break Helm)\n\t\t\t// We check that all ChangeTransferPolicy references are properly wrapped\n\t\t\t// Pattern checks for: default: \"...<text>{{ .ChangeTransferPolicy\" (not escaped)\n\t\t\t// The properly escaped version is: default: \"...{{ \"{{ .ChangeTransferPolicy...\" }}\"\n\t\t\tExpect(result).NotTo(MatchRegexp(`default:\\s+\"[^{]*\\{\\{\\s*\\.ChangeTransferPolicy`),\n\t\t\t\t\"unescaped Go templates should not exist in default values\")\n\t\t})\n\n\t\tIt(\"should escape multiple template expressions on the same line\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"policies.example.com\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nspec:\n  versions:\n  - schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            properties:\n              message:\n                default: \"From {{ .Source.Branch }} to {{ .Target.Branch }}\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// Both templates should be escaped (applies to all resources)\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Source.Branch }}\" }}`))\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Target.Branch }}\" }}`))\n\t\t})\n\n\t\tIt(\"should escape templates with special characters\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"configs.example.com\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nspec:\n  versions:\n  - schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            properties:\n              value:\n                default: \"Value: {{ .Config.Key-With-Dashes }}\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Config.Key-With-Dashes }}\" }}`))\n\t\t})\n\n\t\tIt(\"should handle template syntax with quotes correctly\", func() {\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"messages.example.com\")\n\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nspec:\n  versions:\n  - schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            properties:\n              template:\n                default: '{{ .Config.Message \"default\" }}'`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// Quotes inside templates should be escaped\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Config.Message \\\"default\\\" }}\" }}`))\n\t\t})\n\n\t\tIt(\"should not double-escape quotes already escaped by yaml.Marshal in double-quoted YAML scalars\", func() {\n\t\t\t// Regression test: yaml.Marshal represents literal \" inside a {{ }} expression as \\\"\n\t\t\t// in a double-quoted YAML scalar, so a second pass escaping \" → \\\" produced \\\\\" which\n\t\t\t// broke Helm's template parser by closing the string literal early (U+002D '-' error).\n\t\t\tcrdResource := &unstructured.Unstructured{}\n\t\t\tcrdResource.SetAPIVersion(\"apiextensions.k8s.io/v1\")\n\t\t\tcrdResource.SetKind(\"CustomResourceDefinition\")\n\t\t\tcrdResource.SetName(\"webrequestcommitstatuses.promoter.argoproj.io\")\n\n\t\t\t// Simulate the raw YAML text that yaml.Marshal produces for a double-quoted scalar\n\t\t\t// containing a \" character – the inner \" appears as \\\" in the YAML text.\n\t\t\tcontent := `apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nspec:\n  versions:\n  - schema:\n      openAPIV3Schema:\n        properties:\n          spec:\n            description: \"example: {{ index .NamespaceMetadata.Labels \\\"asset-id\\\" }}\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, crdResource)\n\n\t\t\t// The escaped form must be valid Go template syntax: \\\" (single backslash+quote),\n\t\t\t// NOT \\\\\" (double backslash+quote) which would terminate the string literal early.\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ index .NamespaceMetadata.Labels \\\"asset-id\\\" }}\" }}`),\n\t\t\t\t\"pre-escaped YAML quotes must not be double-escaped to \\\\\\\\ which breaks Helm template parsing\")\n\t\t\tExpect(result).NotTo(ContainSubstring(`\\\\\"asset-id\\\\\"`),\n\t\t\t\t\"double-escaped quotes (\\\\\\\\\\\") must not appear in the output\")\n\t\t})\n\n\t\tIt(\"should escape templates in ConfigMaps and other non-CRD resources\", func() {\n\t\t\tconfigMapResource := &unstructured.Unstructured{}\n\t\t\tconfigMapResource.SetAPIVersion(\"v1\")\n\t\t\tconfigMapResource.SetKind(\"ConfigMap\")\n\t\t\tconfigMapResource.SetName(\"template-config\")\n\t\t\tconfigMapResource.SetNamespace(\"test-project-system\")\n\n\t\t\t// ANY resource can have Go template syntax that needs escaping\n\t\t\t// Examples: ConfigMaps with notification templates, Secrets with webhook URLs,\n\t\t\t// Deployment annotations with CI/CD metadata, etc.\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: template-config\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/name: test-project\ndata:\n  notification: \"Deployed from {{ .Source.Branch }} to {{ .Target.Branch }}\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, configMapResource)\n\n\t\t\t// Existing templates should be escaped (applies to ALL resources, not just CRDs)\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Source.Branch }}\" }}`))\n\t\t\tExpect(result).To(ContainSubstring(`{{ \"{{ .Target.Branch }}\" }}`))\n\n\t\t\t// Helm templates should still be added normally\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t\tExpect(result).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`))\n\t\t})\n\n\t\tIt(\"should handle content without any templates\", func() {\n\t\t\tconfigMapResource := &unstructured.Unstructured{}\n\t\t\tconfigMapResource.SetAPIVersion(\"v1\")\n\t\t\tconfigMapResource.SetKind(\"ConfigMap\")\n\t\t\tconfigMapResource.SetName(\"no-template\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\ndata:\n  message: \"No templates here\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, configMapResource)\n\n\t\t\t// Should not add any escaping\n\t\t\tExpect(result).To(ContainSubstring(`message: \"No templates here\"`))\n\t\t\tExpect(result).NotTo(ContainSubstring(`{{ \"{{`))\n\t\t})\n\t})\n\n\tContext(\"edge cases\", func() {\n\t\tIt(\"should handle empty content\", func() {\n\t\t\ttestResource := &unstructured.Unstructured{}\n\t\t\ttestResource.SetKind(\"ConfigMap\")\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(\"\", testResource)\n\t\t\tExpect(result).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should handle resources without namespace\", func() {\n\t\t\tclusterRoleResource := &unstructured.Unstructured{}\n\t\t\tclusterRoleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\tclusterRoleResource.SetKind(\"ClusterRole\")\n\t\t\tclusterRoleResource.SetName(\"test-project-manager-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, clusterRoleResource)\n\n\t\t\t// Should not add namespace substitution for cluster-scoped resources\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace:\"))\n\t\t})\n\n\t\tIt(\"should handle malformed YAML gracefully\", func() {\n\t\t\ttestResource := &unstructured.Unstructured{}\n\t\t\ttestResource.SetKind(\"ConfigMap\")\n\n\t\t\tmalformedContent := \"not: valid: yaml: content:\"\n\t\t\tresult := templater.ApplyHelmSubstitutions(malformedContent, testResource)\n\n\t\t\t// Should return content as-is for malformed YAML\n\t\t\tExpect(result).To(Equal(malformedContent))\n\t\t})\n\t})\n\n\tContext(\"namespace-scoped RBAC resources\", func() {\n\t\tIt(\"should preserve explicit namespace in Role for cross-namespace permissions\", func() {\n\t\t\troleResource := &unstructured.Unstructured{}\n\t\t\troleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleResource.SetKind(\"Role\")\n\t\t\troleResource.SetName(\"test-project-manager-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-manager-role\n  namespace: infrastructure\n  labels:\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - get\n  - list\n  - patch\n  - update\n  - watch`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, roleResource)\n\n\t\t\t// Namespace should be preserved (not templated) for cross-namespace permissions\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: infrastructure\"),\n\t\t\t\t\"explicit namespace should be preserved for cross-namespace Role\")\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"explicit namespace should NOT be templated to Release.Namespace\")\n\n\t\t\t// Labels should still be templated\n\t\t\tExpect(result).To(ContainSubstring(`app.kubernetes.io/name: {{ include \"test-project.name\" . }}`))\n\n\t\t\t// Name should be templated\n\t\t\tExpect(result).To(ContainSubstring(`name: {{ include \"test-project.resourceName\"`))\n\n\t\t\t// Rules should be preserved\n\t\t\tExpect(result).To(ContainSubstring(\"- apps\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- deployments\"))\n\t\t})\n\n\t\tIt(\"should preserve explicit namespace in Role for leader election\", func() {\n\t\t\troleResource := &unstructured.Unstructured{}\n\t\t\troleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleResource.SetKind(\"Role\")\n\t\t\troleResource.SetName(\"test-project-leader-election-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: production\n  labels:\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n  - update`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, roleResource)\n\n\t\t\t// Namespace should be preserved for cross-namespace leader election\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: production\"),\n\t\t\t\t\"explicit namespace should be preserved for cross-namespace leader election Role\")\n\n\t\t\t// Verify leader election permissions\n\t\t\tExpect(result).To(ContainSubstring(\"- coordination.k8s.io\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- leases\"))\n\t\t\tExpect(result).To(ContainSubstring(\"- events\"))\n\t\t})\n\n\t\tIt(\"should preserve explicit namespace in RoleBinding metadata\", func() {\n\t\t\troleBindingResource := &unstructured.Unstructured{}\n\t\t\troleBindingResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleBindingResource.SetKind(\"RoleBinding\")\n\t\t\troleBindingResource.SetName(\"test-project-manager-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  namespace: infrastructure\n  labels:\n    app.kubernetes.io/name: test-project\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, roleBindingResource)\n\n\t\t\t// RoleBinding metadata namespace should be preserved\n\t\t\tExpect(result).To(ContainSubstring(\"metadata:\\n  name:\"))\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: infrastructure\"),\n\t\t\t\t\"RoleBinding metadata namespace should be preserved\")\n\n\t\t\t// Subject namespace should be templated (references the controller namespace)\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"subject namespace should be templated to Release.Namespace\")\n\n\t\t\t// Name references should be templated\n\t\t\tExpect(result).To(ContainSubstring(`name: {{ include \"test-project.resourceName\"`))\n\t\t})\n\n\t\tIt(\"should template Role namespace when it matches project namespace\", func() {\n\t\t\troleResource := &unstructured.Unstructured{}\n\t\t\troleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleResource.SetKind(\"Role\")\n\t\t\troleResource.SetName(\"test-project-leader-election-role\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: test-project-system\n  labels:\n    app.kubernetes.io/name: test-project\nrules:\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, roleResource)\n\n\t\t\t// When namespace matches project namespace, it should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"Role namespace should be templated when it matches project namespace\")\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace: test-project-system\"),\n\t\t\t\t\"project namespace should be templated, not preserved\")\n\t\t})\n\n\t\tIt(\"should handle RoleBinding with multiple subjects correctly\", func() {\n\t\t\troleBindingResource := &unstructured.Unstructured{}\n\t\t\troleBindingResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleBindingResource.SetKind(\"RoleBinding\")\n\t\t\troleBindingResource.SetName(\"test-project-manager-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  namespace: infrastructure\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n- kind: ServiceAccount\n  name: test-project-webhook\n  namespace: test-project-system`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, roleBindingResource)\n\n\t\t\t// Both subject namespaces should be templated\n\t\t\tsubjectNamespaceCount := strings.Count(result, \"namespace: {{ .Release.Namespace }}\")\n\t\t\tExpect(subjectNamespaceCount).To(BeNumerically(\">=\", 2),\n\t\t\t\t\"both subject namespaces should be templated\")\n\n\t\t\t// RoleBinding metadata namespace should be preserved\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: infrastructure\"))\n\t\t})\n\n\t\tIt(\"should preserve resource names when namespace appears as substring\", func() {\n\t\t\t// Critical: namespace \"user\" must NOT break resource name \"users\"\n\t\t\t// This validates field-aware replacement prevents substring corruption\n\t\t\troleResource := &unstructured.Unstructured{}\n\t\t\troleResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleResource.SetKind(\"ClusterRole\")\n\t\t\troleResource.SetName(\"manager-role\")\n\n\t\t\t// Scenario: manager namespace is \"user\", CRD resource is \"users\"\n\t\t\tcustomTemplater := &HelmTemplater{\n\t\t\t\tdetectedPrefix:   \"test-project\",\n\t\t\t\tchartName:        \"test-project\",\n\t\t\t\tmanagerNamespace: \"user\", // Short namespace that appears as substring\n\t\t\t}\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role\nrules:\n- apiGroups:\n  - identity.example.com\n  resources:\n  - users\n  - users/finalizers\n  - users/status\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete`\n\n\t\t\tresult := customTemplater.ApplyHelmSubstitutions(content, roleResource)\n\n\t\t\t// Critical: resource name \"users\" must NOT be replaced with \"{{ .Release.Namespace }}s\"\n\t\t\tExpect(result).To(ContainSubstring(\"- users\"),\n\t\t\t\t\"resource name 'users' should remain unchanged\")\n\t\t\tExpect(result).To(ContainSubstring(\"- users/finalizers\"),\n\t\t\t\t\"resource name 'users/finalizers' should remain unchanged\")\n\t\t\tExpect(result).To(ContainSubstring(\"- users/status\"),\n\t\t\t\t\"resource name 'users/status' should remain unchanged\")\n\n\t\t\t// Ensure we didn't create templated resource names\n\t\t\tExpect(result).NotTo(ContainSubstring(\"- {{ .Release.Namespace }}s\"),\n\t\t\t\t\"must NOT replace 'user' substring in resource names\")\n\t\t\tExpect(result).NotTo(MatchRegexp(`resources:\\s*-\\s*\\{\\{.*\\}\\}`),\n\t\t\t\t\"resource names must never be templated\")\n\t\t})\n\n\t\tIt(\"should handle edge case where namespace is substring of multiple fields\", func() {\n\t\t\t// Test more edge cases: namespace \"app\" appears in \"applications\", \"apps\", etc.\n\t\t\tcustomTemplater := &HelmTemplater{\n\t\t\t\tdetectedPrefix:   \"test-project\",\n\t\t\t\tchartName:        \"test-project\",\n\t\t\t\tmanagerNamespace: \"app\",\n\t\t\t}\n\n\t\t\troleBindingResource := &unstructured.Unstructured{}\n\t\t\troleBindingResource.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\troleBindingResource.SetKind(\"RoleBinding\")\n\t\t\troleBindingResource.SetName(\"manager-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  namespace: app\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: app`\n\n\t\t\tresult := customTemplater.ApplyHelmSubstitutions(content, roleBindingResource)\n\n\t\t\t// Namespace fields should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"),\n\t\t\t\t\"namespace fields should be templated\")\n\n\t\t\t// Verify we have exactly 2 namespace template substitutions (metadata + subject)\n\t\t\tnamespaceTemplateCount := strings.Count(result, \"namespace: {{ .Release.Namespace }}\")\n\t\t\tExpect(namespaceTemplateCount).To(Equal(2),\n\t\t\t\t\"should have exactly 2 namespace field replacements\")\n\n\t\t\t// Verify apiGroup field is NOT affected (contains \"app\" in \"rbac.authorization.k8s.io\")\n\t\t\tExpect(result).To(ContainSubstring(\"apiGroup: rbac.authorization.k8s.io\"),\n\t\t\t\t\"apiGroup should not be affected by namespace replacement\")\n\t\t})\n\n\t\tIt(\"should handle ALL Kubernetes DNS patterns generically\", func() {\n\t\t\t// This test validates DNS replacement works for ANY K8s DNS pattern\n\t\t\tconfigMapResource := &unstructured.Unstructured{}\n\t\t\tconfigMapResource.SetAPIVersion(\"v1\")\n\t\t\tconfigMapResource.SetKind(\"ConfigMap\")\n\t\t\tconfigMapResource.SetName(\"dns-config\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: dns-config\n  namespace: test-project-system\ndata:\n  # Standard service DNS\n  service-short: api.test-project-system.svc\n  service-full: api.test-project-system.svc.cluster.local\n  service-port: api.test-project-system.svc:8080\n  service-path: https://api.test-project-system.svc.cluster.local:443/v1\n  \n  # Pod DNS\n  pod-dns: my-pod.test-project-system.pod.cluster.local\n  \n  # Endpoints DNS  \n  endpoints-dns: my-service.test-project-system.endpoints.cluster.local\n  \n  # Headless service (StatefulSet)\n  stateful-0: app-0.app-headless.test-project-system.svc.cluster.local\n  stateful-1: app-1.app-headless.test-project-system.svc.cluster.local\n  \n  # External namespace should be preserved\n  external-svc: monitoring.monitoring-system.svc.cluster.local`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, configMapResource)\n\n\t\t\t// Verify ALL manager namespace DNS patterns are templated\n\t\t\tExpect(result).To(ContainSubstring(\"api.{{ .Release.Namespace }}.svc\"))\n\t\t\tExpect(result).To(ContainSubstring(\"api.{{ .Release.Namespace }}.svc.cluster.local\"))\n\t\t\tExpect(result).To(ContainSubstring(\"api.{{ .Release.Namespace }}.svc:8080\"))\n\t\t\tExpect(result).To(ContainSubstring(\"api.{{ .Release.Namespace }}.svc.cluster.local:443\"))\n\t\t\tExpect(result).To(ContainSubstring(\"my-pod.{{ .Release.Namespace }}.pod.cluster.local\"))\n\t\t\tExpect(result).To(ContainSubstring(\"my-service.{{ .Release.Namespace }}.endpoints.cluster.local\"))\n\t\t\tExpect(result).To(ContainSubstring(\"app-0.app-headless.{{ .Release.Namespace }}.svc.cluster.local\"))\n\t\t\tExpect(result).To(ContainSubstring(\"app-1.app-headless.{{ .Release.Namespace }}.svc.cluster.local\"))\n\n\t\t\t// Verify NO hardcoded manager namespace remains\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.pod\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.endpoints\"))\n\n\t\t\t// Verify external namespace is preserved\n\t\t\tExpect(result).To(ContainSubstring(\"monitoring.monitoring-system.svc.cluster.local\"))\n\t\t})\n\n\t\tIt(\"should NOT replace namespace-like strings in non-DNS contexts\", func() {\n\t\t\t// Edge case: ensure we don't break strings that happen to contain the namespace\n\t\t\tconfigMapResource := &unstructured.Unstructured{}\n\t\t\tconfigMapResource.SetAPIVersion(\"v1\")\n\t\t\tconfigMapResource.SetKind(\"ConfigMap\")\n\t\t\tconfigMapResource.SetName(\"edge-cases\")\n\n\t\t\t// Using \"app\" as namespace to test substring issues\n\t\t\tcustomTemplater := &HelmTemplater{\n\t\t\t\tdetectedPrefix:   \"test\",\n\t\t\t\tchartName:        \"test\",\n\t\t\t\tmanagerNamespace: \"app\",\n\t\t\t}\n\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: edge-cases\n  namespace: app\ndata:\n  # DNS patterns - should be templated\n  service-url: http://api.app.svc:8080\n  \n  # NOT DNS patterns - should be preserved\n  app-name: \"my-application\"\n  app-version: \"v1.2.3\"\n  mapping: \"application-mapping\"\n  labels: \"app=frontend,app.kubernetes.io/name=myapp\"\n  \n  # Tricky: \"app\" in various contexts\n  erapplication: \"some-value\"\n  wrapperapp: \"another-value\"`\n\n\t\t\tresult := customTemplater.ApplyHelmSubstitutions(content, configMapResource)\n\n\t\t\t// DNS pattern should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"api.{{ .Release.Namespace }}.svc:8080\"))\n\n\t\t\t// Non-DNS occurrences should be preserved\n\t\t\tExpect(result).To(ContainSubstring(`app-name: \"my-application\"`))\n\t\t\tExpect(result).To(ContainSubstring(\"app-version\"))\n\t\t\tExpect(result).To(ContainSubstring(\"mapping: \\\"application-mapping\\\"\"))\n\t\t\tExpect(result).To(ContainSubstring(\"app=frontend\"))\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name=myapp\"))\n\t\t\tExpect(result).To(ContainSubstring(\"erapplication\"))\n\t\t\tExpect(result).To(ContainSubstring(\"wrapperapp\"))\n\n\t\t\t// Namespace field should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t})\n\n\t\tIt(\"should handle ANY resource type with namespace references (generic test)\", func() {\n\t\t\t// This test validates that namespace replacement is GENERIC and works\n\t\t\t// for any resource type, including custom resources in extras/ directory\n\n\t\t\t// Test with a custom ConfigMap (common in extras/)\n\t\t\tconfigMapResource := &unstructured.Unstructured{}\n\t\t\tconfigMapResource.SetAPIVersion(\"v1\")\n\t\t\tconfigMapResource.SetKind(\"ConfigMap\")\n\t\t\tconfigMapResource.SetName(\"custom-config\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: custom-config\n  namespace: test-project-system\ndata:\n  service-url: http://api-service.test-project-system.svc.cluster.local:8080\n  webhook-endpoint: https://webhook.test-project-system.svc:9443/validate\n  annotation-ref: \"test-project-system/my-resource\"`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, configMapResource)\n\n\t\t\t// 1. Namespace field should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace: test-project-system\"))\n\n\t\t\t// 2. DNS names in data values should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"http://api-service.{{ .Release.Namespace }}.svc.cluster.local:8080\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc.cluster.local\"))\n\n\t\t\t// 3. DNS names with ports should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"https://webhook.{{ .Release.Namespace }}.svc:9443\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc:9443\"))\n\n\t\t\t// 4. Annotation-style references should be templated\n\t\t\tExpect(result).To(ContainSubstring(\"{{ .Release.Namespace }}/my-resource\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"test-project-system/my-resource\"))\n\t\t})\n\n\t\tIt(\"should handle Secret with namespace references\", func() {\n\t\t\tsecretResource := &unstructured.Unstructured{}\n\t\t\tsecretResource.SetAPIVersion(\"v1\")\n\t\t\tsecretResource.SetKind(\"Secret\")\n\t\t\tsecretResource.SetName(\"app-secret\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Secret\nmetadata:\n  name: app-secret\n  namespace: test-project-system\n  annotations:\n    source: test-project-system/config\nstringData:\n  database-url: postgresql://db.test-project-system.svc:5432/mydb\n  redis-url: redis://cache.test-project-system.svc.cluster.local:6379`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, secretResource)\n\n\t\t\t// Namespace field templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\n\t\t\t// Annotation value templated\n\t\t\tExpect(result).To(ContainSubstring(\"source: {{ .Release.Namespace }}/config\"))\n\n\t\t\t// DNS names in data templated\n\t\t\tExpect(result).To(ContainSubstring(\"postgresql://db.{{ .Release.Namespace }}.svc:5432\"))\n\t\t\tExpect(result).To(ContainSubstring(\"redis://cache.{{ .Release.Namespace }}.svc.cluster.local:6379\"))\n\n\t\t\t// No hardcoded namespace remains\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\t\t})\n\n\t\tIt(\"should handle Ingress with namespace references\", func() {\n\t\t\tingressResource := &unstructured.Unstructured{}\n\t\t\tingressResource.SetAPIVersion(\"networking.k8s.io/v1\")\n\t\t\tingressResource.SetKind(\"Ingress\")\n\t\t\tingressResource.SetName(\"app-ingress\")\n\n\t\t\tcontent := `apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: app-ingress\n  namespace: test-project-system\n  annotations:\n    nginx.ingress.kubernetes.io/auth-url: http://auth.test-project-system.svc.cluster.local/verify\n    cert-manager.io/issuer: test-project-system/letsencrypt\nspec:\n  rules:\n  - host: example.com\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: app-service\n            port:\n              number: 80`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, ingressResource)\n\n\t\t\t// Namespace field templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\n\t\t\t// Annotation DNS templated\n\t\t\tExpect(result).To(ContainSubstring(\"http://auth.{{ .Release.Namespace }}.svc.cluster.local/verify\"))\n\n\t\t\t// Annotation reference templated\n\t\t\tExpect(result).To(ContainSubstring(\"cert-manager.io/issuer: {{ .Release.Namespace }}/letsencrypt\"))\n\n\t\t\t// No hardcoded namespace\n\t\t\tExpect(result).NotTo(ContainSubstring(\"test-project-system/\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\t\t})\n\n\t\tIt(\"should handle PodMonitor with namespace references\", func() {\n\t\t\tpodMonitorResource := &unstructured.Unstructured{}\n\t\t\tpodMonitorResource.SetAPIVersion(\"monitoring.coreos.com/v1\")\n\t\t\tpodMonitorResource.SetKind(\"PodMonitor\")\n\t\t\tpodMonitorResource.SetName(\"app-monitor\")\n\n\t\t\tcontent := `apiVersion: monitoring.coreos.com/v1\nkind: PodMonitor\nmetadata:\n  name: app-monitor\n  namespace: test-project-system\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  podMetricsEndpoints:\n  - port: metrics\n    scheme: https\n    tlsConfig:\n      serverName: metrics.test-project-system.svc\n      ca:\n        configMap:\n          name: prometheus-ca\n          namespace: test-project-system`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, podMonitorResource)\n\n\t\t\t// Namespace field templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\n\t\t\t// ServerName DNS templated\n\t\t\tExpect(result).To(ContainSubstring(\"serverName: metrics.{{ .Release.Namespace }}.svc\"))\n\n\t\t\t// ConfigMap namespace reference templated\n\t\t\tnamespaceCount := strings.Count(result, \"namespace: {{ .Release.Namespace }}\")\n\t\t\tExpect(namespaceCount).To(Equal(2), \"both metadata and configMap namespace should be templated\")\n\n\t\t\t// No hardcoded namespace in DNS\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\t\t})\n\n\t\tIt(\"should handle custom CRD with multiple namespace contexts\", func() {\n\t\t\tcustomResource := &unstructured.Unstructured{}\n\t\t\tcustomResource.SetAPIVersion(\"example.com/v1\")\n\t\t\tcustomResource.SetKind(\"Application\")\n\t\t\tcustomResource.SetName(\"my-app\")\n\n\t\t\tcontent := `apiVersion: example.com/v1\nkind: Application\nmetadata:\n  name: my-app\n  namespace: test-project-system\n  annotations:\n    backup.velero.io/backup-volumes: test-project-system/pvc\nspec:\n  database:\n    host: postgres.test-project-system.svc.cluster.local\n    port: 5432\n  messaging:\n    brokerURL: amqp://rabbitmq.test-project-system.svc:5672\n  externalServices:\n    - name: external-api\n      url: https://api.external-namespace.svc/v1\n  references:\n    configMapRef: test-project-system/app-config\n    secretRef: test-project-system/app-secret`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, customResource)\n\n\t\t\t// Namespace field templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\n\t\t\t// Annotation reference templated\n\t\t\tExpect(result).To(ContainSubstring(\"{{ .Release.Namespace }}/pvc\"))\n\n\t\t\t// DNS names templated\n\t\t\tExpect(result).To(ContainSubstring(\"postgres.{{ .Release.Namespace }}.svc.cluster.local\"))\n\t\t\tExpect(result).To(ContainSubstring(\"rabbitmq.{{ .Release.Namespace }}.svc:5672\"))\n\n\t\t\t// External namespace preserved (not manager namespace)\n\t\t\tExpect(result).To(ContainSubstring(\"https://api.external-namespace.svc/v1\"))\n\n\t\t\t// ConfigMap/Secret refs templated\n\t\t\tExpect(result).To(ContainSubstring(\"configMapRef: {{ .Release.Namespace }}/app-config\"))\n\t\t\tExpect(result).To(ContainSubstring(\"secretRef: {{ .Release.Namespace }}/app-secret\"))\n\n\t\t\t// No manager namespace remains\n\t\t\tExpect(result).NotTo(ContainSubstring(\"test-project-system/\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\t\t})\n\n\t\tIt(\"should NOT replace namespace in non-manager context\", func() {\n\t\t\t// Critical: cross-namespace references must be preserved\n\t\t\tcustomResource := &unstructured.Unstructured{}\n\t\t\tcustomResource.SetAPIVersion(\"v1\")\n\t\t\tcustomResource.SetKind(\"ConfigMap\")\n\t\t\tcustomResource.SetName(\"federation-config\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: federation-config\n  namespace: test-project-system\ndata:\n  clusters: |\n    - name: cluster-a\n      apiserver: https://api.cluster-a-system.svc:6443\n    - name: cluster-b\n      apiserver: https://api.cluster-b-system.svc:6443\n  external-service: https://monitoring.monitoring-system.svc.cluster.local:9090\n  internal-service: https://internal.test-project-system.svc:8080`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, customResource)\n\n\t\t\t// Manager namespace field templated\n\t\t\tExpect(result).To(ContainSubstring(\"namespace: {{ .Release.Namespace }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"namespace: test-project-system\"))\n\n\t\t\t// Manager namespace in DNS templated (appears once in internal-service)\n\t\t\tExpect(result).To(ContainSubstring(\"internal.{{ .Release.Namespace }}.svc:8080\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\".test-project-system.svc\"))\n\n\t\t\t// External namespaces preserved (these don't match manager namespace)\n\t\t\tExpect(result).To(ContainSubstring(\"cluster-a-system.svc\"))\n\t\t\tExpect(result).To(ContainSubstring(\"cluster-b-system.svc\"))\n\t\t\tExpect(result).To(ContainSubstring(\"monitoring-system.svc\"))\n\t\t})\n\t})\n\n\tContext(\"templatePorts\", func() {\n\t\tIt(\"should template webhook service ports\", func() {\n\t\t\twebhookService := &unstructured.Unstructured{}\n\t\t\twebhookService.SetAPIVersion(\"v1\")\n\t\t\twebhookService.SetKind(\"Service\")\n\t\t\twebhookService.SetName(\"test-project-webhook-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-webhook-service\n  namespace: test-project-system\nspec:\n  ports:\n  - port: 443\n    targetPort: 9443\n    protocol: TCP\n  selector:\n    control-plane: controller-manager`\n\n\t\t\tresult := templater.templatePorts(content, webhookService)\n\n\t\t\t// Should template webhook port\n\t\t\tExpect(result).To(ContainSubstring(\"targetPort: {{ .Values.webhook.port }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"targetPort: 9443\"))\n\t\t})\n\n\t\tIt(\"should template metrics service ports\", func() {\n\t\t\tmetricsService := &unstructured.Unstructured{}\n\t\t\tmetricsService.SetAPIVersion(\"v1\")\n\t\t\tmetricsService.SetKind(\"Service\")\n\t\t\tmetricsService.SetName(\"test-project-controller-manager-metrics-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-controller-manager-metrics-service\n  namespace: test-project-system\nspec:\n  ports:\n  - port: 8443\n    targetPort: 8443\n    protocol: TCP\n    name: https\n  selector:\n    control-plane: controller-manager`\n\n\t\t\tresult := templater.templatePorts(content, metricsService)\n\n\t\t\t// Should template metrics port\n\t\t\tExpect(result).To(ContainSubstring(\"port: {{ .Values.metrics.port }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"targetPort: {{ .Values.metrics.port }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"port: 8443\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"targetPort: 8443\"))\n\t\t})\n\n\t\tIt(\"should template webhook container ports in Deployment\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP`\n\n\t\t\tresult := templater.templatePorts(content, deployment)\n\n\t\t\t// Should template webhook containerPort\n\t\t\tExpect(result).To(ContainSubstring(\"containerPort: {{ .Values.webhook.port }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"containerPort: 9443\"))\n\t\t})\n\n\t\tIt(\"should template health probe ports in Deployment\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081`\n\n\t\t\tresult := templater.templatePorts(content, deployment)\n\n\t\t\tExpect(result).To(ContainSubstring(\"port: 8081\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"{{ .Values\"))\n\t\t})\n\n\t\tIt(\"should template port-related args in Deployment\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        args:\n        - --metrics-bind-address=:8443\n        - --health-probe-bind-address=:8081\n        - --leader-elect`\n\n\t\t\tresult := templater.templatePorts(content, deployment)\n\n\t\t\tExpect(result).To(ContainSubstring(\"--metrics-bind-address=:{{ .Values.metrics.port }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"--metrics-bind-address=:8443\"))\n\t\t\tExpect(result).To(ContainSubstring(\"--health-probe-bind-address=:8081\"))\n\t\t\tExpect(result).To(ContainSubstring(\"--leader-elect\"))\n\t\t})\n\n\t\tIt(\"should template custom port values\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        args:\n        - --metrics-bind-address=:9090\n        - --health-probe-bind-address=:9091\n        - --webhook-port=9444\n        ports:\n        - containerPort: 9444\n          name: webhook-server\n        livenessProbe:\n          httpGet:\n            port: 9091`\n\n\t\t\tresult := templater.templatePorts(content, deployment)\n\n\t\t\tExpect(result).To(ContainSubstring(\"--metrics-bind-address=:{{ .Values.metrics.port }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"--webhook-port={{ .Values.webhook.port }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"containerPort: {{ .Values.webhook.port }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"--health-probe-bind-address=:9091\"))\n\t\t\tExpect(result).To(ContainSubstring(\"port: 9091\"))\n\t\t})\n\n\t\tIt(\"should not template non-webhook/metrics resources\", func() {\n\t\t\tregularService := &unstructured.Unstructured{}\n\t\t\tregularService.SetAPIVersion(\"v1\")\n\t\t\tregularService.SetKind(\"Service\")\n\t\t\tregularService.SetName(\"test-project-some-other-service\")\n\n\t\t\tcontent := `apiVersion: v1\nkind: Service\nmetadata:\n  name: test-project-some-other-service\nspec:\n  ports:\n  - port: 8080\n    targetPort: 8080`\n\n\t\t\tresult := templater.templatePorts(content, regularService)\n\n\t\t\t// Should not template regular service ports\n\t\t\tExpect(result).To(ContainSubstring(\"port: 8080\"))\n\t\t\tExpect(result).To(ContainSubstring(\"targetPort: 8080\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"{{ .Values\"))\n\t\t})\n\t})\n\n\tContext(\"cert-manager resource name templating\", func() {\n\t\tIt(\"should template Certificate resource name with chart.fullname\", func() {\n\t\t\tcert := &unstructured.Unstructured{}\n\t\t\tcert.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tcert.SetKind(\"Certificate\")\n\t\t\tcert.SetName(\"test-project-serving-cert\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: test-project-serving-cert\n  namespace: test-project-system`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, cert)\n\n\t\t\texpectedCert := `name: {{ include \"test-project.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}`\n\t\t\tExpect(result).To(ContainSubstring(expectedCert))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-serving-cert\"))\n\t\t})\n\n\t\tIt(\"should template Issuer resource name with chart.fullname\", func() {\n\t\t\tissuer := &unstructured.Unstructured{}\n\t\t\tissuer.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tissuer.SetKind(\"Issuer\")\n\t\t\tissuer.SetName(\"test-project-selfsigned-issuer\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: test-project-selfsigned-issuer\n  namespace: test-project-system\nspec:\n  selfSigned: {}`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, issuer)\n\n\t\t\tExpect(result).To(ContainSubstring(expectedIssuerName))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-selfsigned-issuer\"))\n\t\t})\n\n\t\tIt(\"should template issuer reference in certificates with chart.fullname\", func() {\n\t\t\tcert := &unstructured.Unstructured{}\n\t\t\tcert.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tcert.SetKind(\"Certificate\")\n\t\t\tcert.SetName(\"test-project-serving-cert\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: test-project-serving-cert\nspec:\n  issuerRef:\n    kind: Issuer\n    name: test-project-selfsigned-issuer`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, cert)\n\n\t\t\tExpect(result).To(ContainSubstring(expectedIssuerName))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-selfsigned-issuer\"))\n\t\t})\n\n\t\tIt(\"should template all resource types generically\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  template:\n    spec:\n      serviceAccountName: test-project-controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// All name fields should use test-project.resourceName\n\t\t\texpectedName := `name: {{ include \"test-project.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}`\n\t\t\texpectedSA := `serviceAccountName: {{ include \"test-project.resourceName\" ` +\n\t\t\t\t`(dict \"suffix\" \"controller-manager\" \"context\" $) }}`\n\t\t\tExpect(result).To(ContainSubstring(expectedName))\n\t\t\tExpect(result).To(ContainSubstring(expectedSA))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: test-project-controller-manager\"))\n\t\t})\n\n\t\tIt(\"should handle custom kustomize prefix\", func() {\n\t\t\tcustomPrefixTemplater := &HelmTemplater{\n\t\t\t\tdetectedPrefix:   \"ln\",           // Custom short prefix from kustomize\n\t\t\t\tchartName:        \"test-project\", // Chart/project name\n\t\t\t\tmanagerNamespace: \"ln-system\",    // Manager namespace\n\t\t\t}\n\n\t\t\tissuer := &unstructured.Unstructured{}\n\t\t\tissuer.SetAPIVersion(\"cert-manager.io/v1\")\n\t\t\tissuer.SetKind(\"Issuer\")\n\t\t\tissuer.SetName(\"ln-selfsigned-issuer\")\n\n\t\t\tcontent := `apiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: ln-selfsigned-issuer\n  labels:\n    app.kubernetes.io/name: ln`\n\n\t\t\tresult := customPrefixTemplater.ApplyHelmSubstitutions(content, issuer)\n\n\t\t\t// Resource name uses test-project.resourceName\n\t\t\tExpect(result).To(ContainSubstring(expectedIssuerName))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"name: ln-selfsigned-issuer\"))\n\t\t\t// Label uses test-project.name\n\t\t\tExpect(result).To(ContainSubstring(\"app.kubernetes.io/name: {{ include \\\"test-project.name\\\" . }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"app.kubernetes.io/name: ln\"))\n\t\t})\n\n\t\tIt(\"should template RoleBinding roleRef and subjects\", func() {\n\t\t\trb := &unstructured.Unstructured{}\n\t\t\trb.SetAPIVersion(\"rbac.authorization.k8s.io/v1\")\n\t\t\trb.SetKind(\"RoleBinding\")\n\t\t\trb.SetName(\"test-project-leader-election-rolebinding\")\n\n\t\t\tcontent := `apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-leader-election-rolebinding\nroleRef:\n  name: test-project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, rb)\n\n\t\t\t// All references should use test-project.resourceName\n\t\t\texpectedRB := `name: {{ include \"test-project.resourceName\" ` +\n\t\t\t\t`(dict \"suffix\" \"leader-election-rolebinding\" \"context\" $) }}`\n\t\t\texpectedRole := `name: {{ include \"test-project.resourceName\" ` +\n\t\t\t\t`(dict \"suffix\" \"leader-election-role\" \"context\" $) }}`\n\t\t\texpectedSA := `name: {{ include \"test-project.resourceName\" ` +\n\t\t\t\t`(dict \"suffix\" \"controller-manager\" \"context\" $) }}`\n\t\t\tExpect(result).To(ContainSubstring(expectedRB))\n\t\t\tExpect(result).To(ContainSubstring(expectedRole))\n\t\t\tExpect(result).To(ContainSubstring(expectedSA))\n\t\t})\n\t})\n\n\tContext(\"custom container name support\", func() {\n\t\tIt(\"should template deployment fields when container name is not 'manager'\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\t// Deployment with custom container name \"controller-test\" using default-container annotation\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\nspec:\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: controller-test\n    spec:\n      containers:\n      - name: controller-test\n        image: controller:latest\n        imagePullPolicy: Always\n        env:\n        - name: POD_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        args:\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n        volumeMounts: []\n      serviceAccountName: controller-manager\n      volumes: []`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// Should template image reference (not hardcoded)\n\t\t\tExpect(result).To(ContainSubstring(\n\t\t\t\t`image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"`))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"image: controller:latest\"))\n\n\t\t\t// Should template imagePullPolicy\n\t\t\tExpect(result).To(ContainSubstring(\"imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"imagePullPolicy: Always\"))\n\n\t\t\t// Should template resources\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.manager.resources }}\"))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- toYaml .Values.manager.resources | nindent\"))\n\n\t\t\t// Env list + envOverrides (--set). Secret refs go in env list.\n\t\t\tExpect(result).To(ContainSubstring(\".Values.manager.env\"))\n\t\t\tExpect(result).To(ContainSubstring(\"toYaml .Values.manager.env\"))\n\t\t\tExpect(result).To(ContainSubstring(\"envOverrides\"))\n\n\t\t\t// Should template args\n\t\t\tExpect(result).To(ContainSubstring(\"{{- range .Values.manager.args }}\"))\n\n\t\t\t// Container name should remain \"controller-test\"\n\t\t\tExpect(result).To(ContainSubstring(\"name: controller-test\"))\n\t\t})\n\n\t\tIt(\"should fall back to 'manager' when default-container annotation is missing\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\t// Deployment without default-container annotation (backward compatibility test)\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        image: controller:latest\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// Should still template fields for \"manager\" container\n\t\t\tExpect(result).To(ContainSubstring(\n\t\t\t\t`image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"`))\n\t\t\tExpect(result).To(ContainSubstring(\"{{- if .Values.manager.resources }}\"))\n\t\t})\n\n\t\tIt(\"should not template when container name doesn't match annotation\", func() {\n\t\t\tdeployment := &unstructured.Unstructured{}\n\t\t\tdeployment.SetAPIVersion(\"apps/v1\")\n\t\t\tdeployment.SetKind(\"Deployment\")\n\t\t\tdeployment.SetName(\"test-project-controller-manager\")\n\n\t\t\t// Deployment with mismatched annotation and container name\n\t\t\tcontent := `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: test-project-controller-manager\nspec:\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: main-container\n    spec:\n      containers:\n      - name: sidecar\n        image: sidecar:latest\n        resources:\n          limits:\n            cpu: 100m`\n\n\t\t\tresult := templater.ApplyHelmSubstitutions(content, deployment)\n\n\t\t\t// Should NOT template sidecar container (doesn't match annotation)\n\t\t\tExpect(result).To(ContainSubstring(\"image: sidecar:latest\"))\n\t\t\tExpect(result).NotTo(ContainSubstring(\"{{ .Values.manager.image.repository }}\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"go.yaml.in/yaml/v3\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\n// ParsedResources holds Kubernetes resources organized by type for Helm chart generation\ntype ParsedResources struct {\n\t// Core Kubernetes resources\n\tNamespace  *unstructured.Unstructured\n\tDeployment *unstructured.Unstructured\n\tServices   []*unstructured.Unstructured\n\n\t// RBAC resources\n\tServiceAccount      *unstructured.Unstructured\n\tRoles               []*unstructured.Unstructured\n\tClusterRoles        []*unstructured.Unstructured\n\tRoleBindings        []*unstructured.Unstructured\n\tClusterRoleBindings []*unstructured.Unstructured\n\n\t// CRD and API resources\n\tCustomResourceDefinitions []*unstructured.Unstructured\n\tWebhookConfigurations     []*unstructured.Unstructured\n\n\t// Custom Resource instances (samples) - instances of the CRDs defined in this project\n\t// These should go to samples/ directory for manual post-install, not be installed by Helm\n\tCustomResources []*unstructured.Unstructured\n\n\t// Cert-manager resources\n\tCertificates []*unstructured.Unstructured\n\tIssuer       *unstructured.Unstructured\n\n\t// Monitoring resources\n\tServiceMonitors []*unstructured.Unstructured\n\n\t// Other resources not fitting above categories\n\tOther []*unstructured.Unstructured\n}\n\n// Parser parses kustomize output and extracts resources by type\ntype Parser struct {\n\tfilePath string\n}\n\n// NewParser creates a new parser for the given kustomize output file\nfunc NewParser(filePath string) *Parser {\n\treturn &Parser{filePath: filePath}\n}\n\n// Parse reads and parses the kustomize output file into organized resource groups\nfunc (p *Parser) Parse() (*ParsedResources, error) {\n\tfile, err := os.Open(p.filePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open file %s: %w\", p.filePath, err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\treturn p.ParseFromReader(file)\n}\n\n// ParseFromReader parses multi-document YAML from a reader and categorizes resources by type\nfunc (p *Parser) ParseFromReader(reader io.Reader) (*ParsedResources, error) {\n\tdecoder := yaml.NewDecoder(reader)\n\tresources := &ParsedResources{\n\t\tCustomResourceDefinitions: make([]*unstructured.Unstructured, 0),\n\t\tRoles:                     make([]*unstructured.Unstructured, 0),\n\t\tClusterRoles:              make([]*unstructured.Unstructured, 0),\n\t\tRoleBindings:              make([]*unstructured.Unstructured, 0),\n\t\tClusterRoleBindings:       make([]*unstructured.Unstructured, 0),\n\t\tServices:                  make([]*unstructured.Unstructured, 0),\n\t\tCertificates:              make([]*unstructured.Unstructured, 0),\n\t\tWebhookConfigurations:     make([]*unstructured.Unstructured, 0),\n\t\tServiceMonitors:           make([]*unstructured.Unstructured, 0),\n\t\tCustomResources:           make([]*unstructured.Unstructured, 0),\n\t\tOther:                     make([]*unstructured.Unstructured, 0),\n\t}\n\n\tfor {\n\t\tvar doc map[string]any\n\t\terr := decoder.Decode(&doc)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode YAML document: %w\", err)\n\t\t}\n\n\t\t// Skip empty documents\n\t\tif doc == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tobj := &unstructured.Unstructured{Object: doc}\n\t\tp.categorizeResource(obj, resources)\n\t}\n\n\t// After parsing all resources, identify Custom Resources by matching against CRD API groups\n\tp.identifyCustomResources(resources)\n\n\treturn resources, nil\n}\n\n// categorizeResource sorts a Kubernetes resource into the appropriate category based on kind and API version\nfunc (p *Parser) categorizeResource(obj *unstructured.Unstructured, resources *ParsedResources) {\n\tkind := obj.GetKind()\n\tapiVersion := obj.GetAPIVersion()\n\n\tswitch {\n\tcase kind == \"Namespace\":\n\t\tresources.Namespace = obj\n\tcase kind == \"CustomResourceDefinition\":\n\t\tresources.CustomResourceDefinitions = append(resources.CustomResourceDefinitions, obj)\n\tcase kind == \"ServiceAccount\":\n\t\tresources.ServiceAccount = obj\n\tcase kind == \"Role\":\n\t\tresources.Roles = append(resources.Roles, obj)\n\tcase kind == \"ClusterRole\":\n\t\tresources.ClusterRoles = append(resources.ClusterRoles, obj)\n\tcase kind == \"RoleBinding\":\n\t\tresources.RoleBindings = append(resources.RoleBindings, obj)\n\tcase kind == \"ClusterRoleBinding\":\n\t\tresources.ClusterRoleBindings = append(resources.ClusterRoleBindings, obj)\n\tcase kind == \"Service\":\n\t\tresources.Services = append(resources.Services, obj)\n\tcase kind == \"Deployment\":\n\t\tresources.Deployment = obj\n\tcase kind == \"Certificate\" && apiVersion == \"cert-manager.io/v1\":\n\t\tresources.Certificates = append(resources.Certificates, obj)\n\tcase kind == \"Issuer\" && apiVersion == \"cert-manager.io/v1\":\n\t\tresources.Issuer = obj\n\tcase kind == \"ValidatingWebhookConfiguration\" || kind == \"MutatingWebhookConfiguration\":\n\t\tresources.WebhookConfigurations = append(resources.WebhookConfigurations, obj)\n\tcase kind == \"ServiceMonitor\" && apiVersion == \"monitoring.coreos.com/v1\":\n\t\tresources.ServiceMonitors = append(resources.ServiceMonitors, obj)\n\tdefault:\n\t\tresources.Other = append(resources.Other, obj)\n\t}\n}\n\n// identifyCustomResources moves resources from Other to CustomResources if they are instances of project CRDs\nfunc (p *Parser) identifyCustomResources(resources *ParsedResources) {\n\t// Build a set of API groups from the CRDs defined in this project\n\tcrdAPIGroups := make(map[string]bool)\n\tfor _, crd := range resources.CustomResourceDefinitions {\n\t\t// Extract the group from the CRD spec\n\t\tgroup, found, err := unstructured.NestedString(crd.Object, \"spec\", \"group\")\n\t\tif found && err == nil && group != \"\" {\n\t\t\tcrdAPIGroups[group] = true\n\t\t}\n\t}\n\n\t// If no CRDs found, nothing to do\n\tif len(crdAPIGroups) == 0 {\n\t\treturn\n\t}\n\n\t// Separate Custom Resources from Other resources\n\tvar remainingOther []*unstructured.Unstructured\n\tfor _, resource := range resources.Other {\n\t\tif resource == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Extract API group from the resource's apiVersion (format: group/version or just version)\n\t\tapiVersion := resource.GetAPIVersion()\n\t\tapiGroup := extractAPIGroup(apiVersion)\n\n\t\t// If this resource's API group matches one of our CRDs, it's a Custom Resource\n\t\tif crdAPIGroups[apiGroup] {\n\t\t\tresources.CustomResources = append(resources.CustomResources, resource)\n\t\t} else {\n\t\t\tremainingOther = append(remainingOther, resource)\n\t\t}\n\t}\n\n\tresources.Other = remainingOther\n}\n\n// GetIgnoredCustomResources returns the list of Custom Resource instances that will be ignored\nfunc (pr *ParsedResources) GetIgnoredCustomResources() []*unstructured.Unstructured {\n\treturn pr.CustomResources\n}\n\n// extractAPIGroup extracts the group from an apiVersion string\n// Examples: \"batch.tutorial.kubebuilder.io/v1\" -> \"batch.tutorial.kubebuilder.io\"\n//\n//\t\"apps/v1\" -> \"apps\"\n//\t\"v1\" -> \"\" (core API group)\nfunc extractAPIGroup(apiVersion string) string {\n\tparts := strings.Split(apiVersion, \"/\")\n\tif len(parts) == 2 {\n\t\treturn parts[0]\n\t}\n\treturn \"\" // Core API group (v1)\n}\n\nfunc (pr *ParsedResources) EstimatePrefix(projectName string) string {\n\tprefix := projectName\n\tif pr.Deployment != nil {\n\t\tif name := pr.Deployment.GetName(); name != \"\" {\n\t\t\tdeploymentPrefix, found := strings.CutSuffix(name, \"-controller-manager\")\n\t\t\tif found {\n\t\t\t\tprefix = deploymentPrefix\n\t\t\t}\n\t\t}\n\t}\n\t// Double check that the prefix is also the prefix for the service names\n\tfor _, svc := range pr.Services {\n\t\tif name := svc.GetName(); name != \"\" {\n\t\t\tif !strings.HasPrefix(name, prefix) {\n\t\t\t\t// If not, fallback to just project name\n\t\t\t\tprefix = projectName\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn prefix\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/parser_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Parser\", func() {\n\tvar (\n\t\tparser   *Parser\n\t\ttempFile string\n\t)\n\n\tBeforeEach(func() {\n\t\t// Create a temporary file for testing\n\t\ttempDir := GinkgoT().TempDir()\n\t\ttempFile = filepath.Join(tempDir, \"test-manifest.yaml\")\n\t})\n\n\tContext(\"with valid YAML containing various resources\", func() {\n\t\tBeforeEach(func() {\n\t\t\tyamlContent := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: test-system\nspec:\n  replicas: 1\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: controller-manager\n  namespace: test-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups: [\"\"]\n  resources: [\"*\"]\n  verbs: [\"*\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: test-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: controller-manager-metrics-service\n  namespace: test-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should parse all resources correctly\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).NotTo(BeNil())\n\n\t\t\t// Check that resources were parsed\n\t\t\tExpect(resources.Namespace).NotTo(BeNil())\n\t\t\tExpect(resources.Deployment).NotTo(BeNil())\n\t\t\tExpect(resources.ServiceAccount).NotTo(BeNil())\n\n\t\t\t// Check RBAC resources\n\t\t\tExpect(resources.ClusterRoles).To(HaveLen(1))\n\t\t\tExpect(resources.ClusterRoleBindings).To(HaveLen(1))\n\n\t\t\t// Check Services\n\t\t\tExpect(resources.Services).To(HaveLen(1))\n\t\t})\n\n\t\tIt(\"should identify correct resource types\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(resources.Namespace.GetKind()).To(Equal(\"Namespace\"))\n\t\t\tExpect(resources.Deployment.GetKind()).To(Equal(\"Deployment\"))\n\t\t\tExpect(resources.ServiceAccount.GetKind()).To(Equal(\"ServiceAccount\"))\n\t\t})\n\t})\n\n\tContext(\"with webhook configuration\", func() {\n\t\tBeforeEach(func() {\n\t\t\tyamlContent := `---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- name: test.example.com\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: test-system\n      path: \"/validate\"\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: serving-cert\n  namespace: test-system\nspec:\n  dnsNames:\n  - webhook-service.test-system.svc\n  - webhook-service.test-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should parse webhook configurations\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(resources.WebhookConfigurations).To(HaveLen(1))\n\t\t\tExpect(resources.Certificates).To(HaveLen(1))\n\n\t\t\twebhook := resources.WebhookConfigurations[0]\n\t\t\tExpect(webhook.GetKind()).To(Equal(\"ValidatingWebhookConfiguration\"))\n\n\t\t\tcert := resources.Certificates[0]\n\t\t\tExpect(cert.GetKind()).To(Equal(\"Certificate\"))\n\t\t})\n\t})\n\n\tContext(\"with ServiceMonitor\", func() {\n\t\tBeforeEach(func() {\n\t\t\tyamlContent := `---\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: controller-manager-metrics-monitor\n  namespace: test-system\nspec:\n  endpoints:\n  - path: /metrics\n    port: https\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should parse ServiceMonitor\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(resources.ServiceMonitors).To(HaveLen(1))\n\n\t\t\tmonitor := resources.ServiceMonitors[0]\n\t\t\tExpect(monitor.GetKind()).To(Equal(\"ServiceMonitor\"))\n\t\t})\n\t})\n\n\tContext(\"with empty or invalid YAML\", func() {\n\t\tIt(\"should handle empty file gracefully\", func() {\n\t\t\terr := os.WriteFile(tempFile, []byte(\"\"), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).NotTo(BeNil())\n\t\t})\n\n\t\tIt(\"should return error for invalid YAML\", func() {\n\t\t\tinvalidYAML := `invalid: yaml: content: [unclosed`\n\t\t\terr := os.WriteFile(tempFile, []byte(invalidYAML), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t\t_, err = parser.Parse()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should return error for non-existent file\", func() {\n\t\t\tparser = NewParser(\"/non/existent/file.yaml\")\n\t\t\t_, err := parser.Parse()\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\t})\n\n\tContext(\"resource organization\", func() {\n\t\tBeforeEach(func() {\n\t\t\tyamlContent := `---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: tests.example.com\nspec:\n  group: example.com\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: selfsigned-issuer\n  namespace: test-system\nspec:\n  selfSigned: {}\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should organize CRDs and Issuers correctly\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(resources.CustomResourceDefinitions).To(HaveLen(1))\n\t\t\tExpect(resources.Issuer).NotTo(BeNil())\n\n\t\t\tcrd := resources.CustomResourceDefinitions[0]\n\t\t\tExpect(crd.GetKind()).To(Equal(\"CustomResourceDefinition\"))\n\n\t\t\tissuer := resources.Issuer\n\t\t\tExpect(issuer.GetKind()).To(Equal(\"Issuer\"))\n\t\t})\n\t})\n\n\tContext(\"with custom prefix\", func() {\n\t\tBeforeEach(func() {\n\t\t\tyamlContent := `---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ln-controller-manager\n  namespace: long-name-test-system\nspec:\n  replicas: 1\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: ln-controller-manager-metrics-service\n  namespace: long-name-test-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    targetPort: 8443\n  selector:\n    control-plane: ln-controller-manager\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should use the correct prefix\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tExpect(resources.EstimatePrefix(\"long-name\")).To(Equal(\"ln\"))\n\t\t})\n\t})\n\n\tContext(\"with multiple namespace-scoped Roles\", func() {\n\t\tBeforeEach(func() {\n\t\t\t// This test validates the parser correctly handles multiple Roles\n\t\t\t// with explicit namespaces (for cross-namespace permissions, leader election, etc.)\n\t\t\tyamlContent := `---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: test-project-system\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: test-project-manager-role\nrules:\n- apiGroups: [\"example.com\"]\n  resources: [\"myresources\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-manager-role\n  namespace: infrastructure\nrules:\n- apiGroups: [\"apps\"]\n  resources: [\"deployments\"]\n  verbs: [\"get\", \"list\", \"patch\", \"update\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: production\nrules:\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\", \"update\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: test-project-leader-election-role\n  namespace: test-project-system\nrules:\n- apiGroups: [\"coordination.k8s.io\"]\n  resources: [\"leases\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-manager-rolebinding\n  namespace: infrastructure\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-leader-election-rolebinding\n  namespace: production\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: test-project-leader-election-rolebinding\n  namespace: test-project-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: test-project-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: test-project-controller-manager\n  namespace: test-project-system\n`\n\t\t\terr := os.WriteFile(tempFile, []byte(yamlContent), 0o600)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tparser = NewParser(tempFile)\n\t\t})\n\n\t\tIt(\"should parse all Roles including those with explicit namespaces\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(resources).NotTo(BeNil())\n\n\t\t\t// Should parse all 3 namespace-scoped Roles\n\t\t\tExpect(resources.Roles).To(HaveLen(3), \"should have 3 namespace-scoped Roles\")\n\n\t\t\t// Should also parse the ClusterRole\n\t\t\tExpect(resources.ClusterRoles).To(HaveLen(1), \"should have 1 ClusterRole\")\n\n\t\t\t// Verify each Role has correct kind\n\t\t\tfor _, role := range resources.Roles {\n\t\t\t\tExpect(role.GetKind()).To(Equal(\"Role\"))\n\t\t\t}\n\n\t\t\t// Verify namespaces are preserved in the parsed objects\n\t\t\tnamespaces := make(map[string]bool)\n\t\t\tfor _, role := range resources.Roles {\n\t\t\t\tns := role.GetNamespace()\n\t\t\t\tExpect(ns).NotTo(BeEmpty(), \"Role should have namespace\")\n\t\t\t\tnamespaces[ns] = true\n\t\t\t}\n\n\t\t\t// Should have Roles in 3 different namespaces\n\t\t\tExpect(namespaces).To(HaveLen(3), \"should have Roles in 3 different namespaces\")\n\t\t\tExpect(namespaces).To(HaveKey(\"infrastructure\"))\n\t\t\tExpect(namespaces).To(HaveKey(\"production\"))\n\t\t\tExpect(namespaces).To(HaveKey(\"test-project-system\"))\n\t\t})\n\n\t\tIt(\"should parse all RoleBindings including those with explicit namespaces\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// Should parse all 3 namespace-scoped RoleBindings\n\t\t\tExpect(resources.RoleBindings).To(HaveLen(3), \"should have 3 RoleBindings\")\n\n\t\t\t// Should also parse the ClusterRoleBinding\n\t\t\tExpect(resources.ClusterRoleBindings).To(HaveLen(1), \"should have 1 ClusterRoleBinding\")\n\n\t\t\t// Verify namespaces are preserved\n\t\t\tnamespaces := make(map[string]bool)\n\t\t\tfor _, rb := range resources.RoleBindings {\n\t\t\t\tns := rb.GetNamespace()\n\t\t\t\tExpect(ns).NotTo(BeEmpty(), \"RoleBinding should have namespace\")\n\t\t\t\tnamespaces[ns] = true\n\t\t\t}\n\n\t\t\tExpect(namespaces).To(HaveLen(3), \"should have RoleBindings in 3 different namespaces\")\n\t\t})\n\n\t\tIt(\"should correctly categorize RBAC resources separately from other resources\", func() {\n\t\t\tresources, err := parser.Parse()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// RBAC resources should be in their specific fields\n\t\t\tExpect(resources.ServiceAccount).NotTo(BeNil())\n\t\t\tExpect(resources.ClusterRoles).To(HaveLen(1))\n\t\t\tExpect(resources.Roles).To(HaveLen(3))\n\t\t\tExpect(resources.ClusterRoleBindings).To(HaveLen(1))\n\t\t\tExpect(resources.RoleBindings).To(HaveLen(3))\n\n\t\t\t// RBAC resources should NOT be in \"Other\"\n\t\t\tExpect(resources.Other).To(BeEmpty(), \"RBAC resources should not be in Other category\")\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/resource_organizer.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\n// ResourceOrganizer groups Kubernetes resources by their logical function\ntype ResourceOrganizer struct {\n\tresources *ParsedResources\n}\n\n// NewResourceOrganizer creates a new resource organizer\nfunc NewResourceOrganizer(resources *ParsedResources) *ResourceOrganizer {\n\treturn &ResourceOrganizer{\n\t\tresources: resources,\n\t}\n}\n\n// OrganizeByFunction groups resources by their logical function matching config/ directory structure\nfunc (o *ResourceOrganizer) OrganizeByFunction() map[string][]*unstructured.Unstructured {\n\tgroups := make(map[string][]*unstructured.Unstructured)\n\n\t// CRDs - Custom Resource Definitions\n\tif len(o.resources.CustomResourceDefinitions) > 0 {\n\t\tgroups[\"crd\"] = o.resources.CustomResourceDefinitions\n\t}\n\n\t// RBAC - Role-Based Access Control resources\n\trbacResources := o.collectRBACResources()\n\tif len(rbacResources) > 0 {\n\t\tgroups[\"rbac\"] = rbacResources\n\t}\n\n\t// Manager - Deployment and related resources\n\tif o.resources.Deployment != nil {\n\t\tgroups[\"manager\"] = []*unstructured.Unstructured{o.resources.Deployment}\n\t}\n\n\t// Metrics - Metrics services and related resources\n\tmetricsResources := o.collectMetricsResources()\n\tif len(metricsResources) > 0 {\n\t\tgroups[\"metrics\"] = metricsResources\n\t}\n\n\t// Webhook - Webhook configurations and webhook services\n\twebhookResources := o.collectWebhookResources()\n\tif len(webhookResources) > 0 {\n\t\tgroups[\"webhook\"] = webhookResources\n\t}\n\n\t// Cert-manager - Certificate issuers and related resources\n\tcertManagerResources := o.collectCertManagerResources()\n\tif len(certManagerResources) > 0 {\n\t\tgroups[\"cert-manager\"] = certManagerResources\n\t}\n\n\t// Prometheus - Prometheus ServiceMonitors and monitoring resources\n\tprometheusResources := o.collectPrometheusResources()\n\tif len(prometheusResources) > 0 {\n\t\tgroups[\"prometheus\"] = prometheusResources\n\t}\n\n\t// Extras - Uncategorized resources (services, configmaps, secrets, etc. not fitting above categories)\n\t// This includes both uncategorized services and all resources from the \"Other\" category\n\textrasResources := o.collectExtrasResources()\n\tif len(extrasResources) > 0 {\n\t\tgroups[\"extras\"] = extrasResources\n\t}\n\n\treturn groups\n}\n\n// collectRBACResources gathers all RBAC-related resources\nfunc (o *ResourceOrganizer) collectRBACResources() []*unstructured.Unstructured {\n\tvar rbacResources []*unstructured.Unstructured\n\n\t// Service account\n\tif o.resources.ServiceAccount != nil {\n\t\trbacResources = append(rbacResources, o.resources.ServiceAccount)\n\t}\n\n\t// Roles and bindings\n\trbacResources = append(rbacResources, o.resources.Roles...)\n\trbacResources = append(rbacResources, o.resources.ClusterRoles...)\n\trbacResources = append(rbacResources, o.resources.RoleBindings...)\n\trbacResources = append(rbacResources, o.resources.ClusterRoleBindings...)\n\n\treturn rbacResources\n}\n\n// collectWebhookResources gathers webhook-related resources\nfunc (o *ResourceOrganizer) collectWebhookResources() []*unstructured.Unstructured {\n\tvar webhookResources []*unstructured.Unstructured\n\n\t// Webhook configurations (ValidatingWebhookConfiguration, MutatingWebhookConfiguration)\n\twebhookResources = append(webhookResources, o.resources.WebhookConfigurations...)\n\n\t// Webhook services (services containing \"webhook\" in the name)\n\tfor _, service := range o.resources.Services {\n\t\tif o.isWebhookService(service) {\n\t\t\twebhookResources = append(webhookResources, service)\n\t\t}\n\t}\n\n\treturn webhookResources\n}\n\n// collectCertManagerResources gathers cert-manager related resources\nfunc (o *ResourceOrganizer) collectCertManagerResources() []*unstructured.Unstructured {\n\tvar certManagerResources []*unstructured.Unstructured\n\n\t// Certificate issuers\n\tif o.resources.Issuer != nil {\n\t\tcertManagerResources = append(certManagerResources, o.resources.Issuer)\n\t}\n\n\t// Certificates (both webhook and metrics certificates are cert-manager resources)\n\tcertManagerResources = append(certManagerResources, o.resources.Certificates...)\n\n\treturn certManagerResources\n}\n\n// collectMetricsResources gathers metrics-related resources\nfunc (o *ResourceOrganizer) collectMetricsResources() []*unstructured.Unstructured {\n\tvar metricsResources []*unstructured.Unstructured\n\n\t// Metrics services (services containing \"metrics\" in the name)\n\tfor _, service := range o.resources.Services {\n\t\tif o.isMetricsService(service) {\n\t\t\tmetricsResources = append(metricsResources, service)\n\t\t}\n\t}\n\n\treturn metricsResources\n}\n\n// collectPrometheusResources gathers prometheus related resources\nfunc (o *ResourceOrganizer) collectPrometheusResources() []*unstructured.Unstructured {\n\tprometheusResources := make([]*unstructured.Unstructured, 0, len(o.resources.ServiceMonitors))\n\n\t// ServiceMonitors\n\tprometheusResources = append(prometheusResources, o.resources.ServiceMonitors...)\n\n\treturn prometheusResources\n}\n\n// isWebhookService determines if a service is webhook-related\n// Verifies KIND is \"Service\" and name ends with \"webhook-service\" suffix\nfunc (o *ResourceOrganizer) isWebhookService(service *unstructured.Unstructured) bool {\n\t// Only match resources with KIND \"Service\" (excludes ServiceAccount, ServiceMonitor, etc.)\n\tif service.GetKind() != kindService {\n\t\treturn false\n\t}\n\tserviceName := service.GetName()\n\t// Use suffix matching to avoid false positives when project names contain \"webhook\"\n\t// e.g., project \"test-helm-no-webhooks\" should not match webhook services\n\treturn strings.HasSuffix(serviceName, \"webhook-service\")\n}\n\n// isMetricsService determines if a service is metrics-related\n// Verifies KIND is \"Service\" and name ends with \"metrics-service\" suffix\nfunc (o *ResourceOrganizer) isMetricsService(service *unstructured.Unstructured) bool {\n\t// Only match resources with KIND \"Service\" (excludes ServiceAccount, ServiceMonitor, etc.)\n\tif service.GetKind() != kindService {\n\t\treturn false\n\t}\n\tserviceName := service.GetName()\n\t// Use suffix matching to avoid false positives when project names contain \"metrics\"\n\treturn strings.HasSuffix(serviceName, \"metrics-service\")\n}\n\n// collectExtrasResources gathers uncategorized resources that don't fit standard categories\nfunc (o *ResourceOrganizer) collectExtrasResources() []*unstructured.Unstructured {\n\tvar extrasResources []*unstructured.Unstructured\n\n\t// Collect services that are neither webhook nor metrics services\n\tfor _, service := range o.resources.Services {\n\t\tif !o.isWebhookService(service) && !o.isMetricsService(service) {\n\t\t\textrasResources = append(extrasResources, service)\n\t\t}\n\t}\n\n\t// Collect all other uncategorized resources (ConfigMaps, Secrets, etc.)\n\textrasResources = append(extrasResources, o.resources.Other...)\n\n\treturn extrasResources\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage kustomize\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestKustomize(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Kustomize Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/consts.go",
    "content": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nconst defaultOutputDir = \"dist\"\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/helpers_tpl.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmHelpers{}\n\n// HelmHelpers scaffolds the _helpers.tpl file for Helm charts\ntype HelmHelpers struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *HelmHelpers) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = \"dist\"\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \"templates\", \"_helpers.tpl\")\n\t}\n\n\tf.TemplateBody = f.generateHelpersTemplate()\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n// generateHelpersTemplate creates the _helpers.tpl content with project-specific template names\nfunc (f *HelmHelpers) generateHelpersTemplate() string {\n\t// Use project name as prefix (e.g., \"project-v4-with-plugins\")\n\t// This creates templates like \"project-v4-with-plugins.name\" instead of generic \"chart.name\"\n\t// preventing collisions when chart is used as a Helm dependency\n\tprefix := f.ProjectName\n\n\treturn fmt.Sprintf(helmHelpersTemplate, prefix, prefix, prefix, prefix, prefix)\n}\n\nconst helmHelpersTemplate = `{{` + \"`\" + `{{/*\nExpand the name of the chart.\n*/}}` + \"`\" + `}}\n{{` + \"`\" + `{{- define \"%s.name\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{` + \"`\" + `{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}` + \"`\" + `}}\n{{` + \"`\" + `{{- define \"%s.fullname\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- if .Values.fullnameOverride }}` + \"`\" + `}}\n{{` + \"`\" + `{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- else }}` + \"`\" + `}}\n{{` + \"`\" + `{{- $name := default .Chart.Name .Values.nameOverride }}` + \"`\" + `}}\n{{` + \"`\" + `{{- if contains $name .Release.Name }}` + \"`\" + `}}\n{{` + \"`\" + `{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- else }}` + \"`\" + `}}\n{{` + \"`\" + `{{- printf \"%%s-%%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{` + \"`\" + `{{/*\nNamespace for generated references.\nAlways uses the Helm release namespace.\n*/}}` + \"`\" + `}}\n{{` + \"`\" + `{{- define \"%s.namespaceName\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- .Release.Namespace }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n\n{{` + \"`\" + `{{/*\nResource name with proper truncation for Kubernetes 63-character limit.\nTakes a dict with:\n  - .suffix: Resource name suffix (e.g., \"metrics\", \"webhook\")\n  - .context: Template context (root context with .Values, .Release, etc.)\nDynamically calculates safe truncation to ensure total name length <= 63 chars.\n*/}}` + \"`\" + `}}\n{{` + \"`\" + `{{- define \"%s.resourceName\" -}}` + \"`\" + `}}\n{{` + \"`\" + `{{- $fullname := include \"%s.fullname\" .context }}` + \"`\" + `}}\n{{` + \"`\" + `{{- $suffix := .suffix }}` + \"`\" + `}}\n{{` + \"`\" + `{{- $maxLen := sub 62 (len $suffix) | int }}` + \"`\" + `}}\n{{` + \"`\" + `{{- if gt (len $fullname) $maxLen }}` + \"`\" + `}}\n{{` + \"`\" + `{{- printf \"%%s-%%s\" (trunc $maxLen $fullname | trimSuffix \"-\") $suffix ` +\n\t`| trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- else }}` + \"`\" + `}}\n{{` + \"`\" + `{{- printf \"%%s-%%s\" $fullname $suffix | trunc 63 | trimSuffix \"-\" }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/notes.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &Notes{}\n\n// Notes scaffolds the NOTES.txt file for Helm charts\ntype Notes struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults sets the default template configuration\nfunc (f *Notes) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = \"dist\"\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \"templates\", \"NOTES.txt\")\n\t}\n\n\tf.TemplateBody = f.generateNotesTemplate()\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n// generateNotesTemplate creates the NOTES.txt content with project-specific information\nfunc (f *Notes) generateNotesTemplate() string {\n\treturn notesTemplate\n}\n\nconst notesTemplate = `Thank you for installing {{` + \"`\" + `{{ .Chart.Name }}` + \"`\" + `}}.\n\nYour release is named {{` + \"`\" + `{{ .Release.Name }}` + \"`\" + `}}.\n\nThe controller and CRDs have been installed in namespace {{` + \"`\" + `{{ .Release.Namespace }}` + \"`\" + `}}.\n\nTo verify the installation:\n\n  kubectl get pods -n {{` + \"`\" + `{{ .Release.Namespace }}` + \"`\" + `}}\n  kubectl get customresourcedefinitions\n\nTo learn more about the release, try:\n\n  $ helm status {{` + \"`\" + `{{ .Release.Name }}` + \"`\" + `}} -n {{` + \"`\" + `{{ .Release.Namespace }}` + \"`\" + `}}\n  $ helm get all {{` + \"`\" + `{{ .Release.Name }}` + \"`\" + `}} -n {{` + \"`\" + `{{ .Release.Namespace }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/notes_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"Notes\", func() {\n\tContext(\"SetTemplateDefaults\", func() {\n\t\tvar notes *Notes\n\n\t\tBeforeEach(func() {\n\t\t\tnotes = &Notes{\n\t\t\t\tOutputDir: \"dist\",\n\t\t\t\tForce:     true,\n\t\t\t}\n\t\t\tnotes.InjectProjectName(\"test-project\")\n\t\t})\n\n\t\tIt(\"should set the correct path\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.Path).To(Equal(\"dist/chart/templates/NOTES.txt\"))\n\t\t})\n\n\t\tIt(\"should use default output dir when not specified\", func() {\n\t\t\tnotes.OutputDir = \"\"\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.Path).To(Equal(\"dist/chart/templates/NOTES.txt\"))\n\t\t})\n\n\t\tIt(\"should set OverwriteFile action when Force is true\", func() {\n\t\t\tnotes.Force = true\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.IfExistsAction).To(Equal(machinery.OverwriteFile))\n\t\t})\n\n\t\tIt(\"should set SkipFile action when Force is false\", func() {\n\t\t\tnotes.Force = false\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.IfExistsAction).To(Equal(machinery.SkipFile))\n\t\t})\n\n\t\tIt(\"should generate template with Helm template syntax\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{ .Chart.Name }}\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{ .Release.Name }}\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{ .Release.Namespace }}\"))\n\t\t})\n\n\t\tIt(\"should include basic installation info\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"Thank you for installing\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"release is named\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"controller and CRDs have been installed\"))\n\t\t})\n\n\t\tIt(\"should include kubectl commands for verification\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"kubectl get pods\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"kubectl get customresourcedefinitions\"))\n\t\t})\n\n\t\tIt(\"should include helm status commands\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"helm status\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"helm get all\"))\n\t\t})\n\n\t\tIt(\"should not contain line numbers or metadata\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Template should be clean without any Go code artifacts\n\t\t\tExpect(notes.TemplateBody).NotTo(ContainSubstring(\"LINE_NUMBER\"))\n\t\t\tExpect(notes.TemplateBody).NotTo(MatchRegexp(`(?m)^\\s*\\d+\\|`))\n\t\t})\n\n\t\tIt(\"should use proper Helm template delimiters\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Check for balanced template delimiters\n\t\t\topenCount := strings.Count(notes.TemplateBody, \"{{\")\n\t\t\tcloseCount := strings.Count(notes.TemplateBody, \"}}\")\n\t\t\tExpect(openCount).To(Equal(closeCount), \"Template should have balanced {{ and }} delimiters\")\n\t\t})\n\n\t\tIt(\"should be concise and generic\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// Should be simple and not overly verbose (reasonable limit for helpful content)\n\t\t\tExpect(len(notes.TemplateBody)).To(BeNumerically(\"<\", 800), \"NOTES.txt should be concise\")\n\t\t})\n\n\t\tIt(\"should generate valid Helm template syntax when processed\", func() {\n\t\t\terr := notes.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t// The template body should use backtick-wrapped syntax for Helm templates\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{`{{ .Chart.Name }}`}}\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{`{{ .Release.Name }}`}}\"))\n\t\t\tExpect(notes.TemplateBody).To(ContainSubstring(\"{{`{{ .Release.Namespace }}`}}\"))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/servicemonitor.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &ServiceMonitor{}\n\n// ServiceMonitor scaffolds a ServiceMonitor for Prometheus monitoring in the Helm chart\ntype ServiceMonitor struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// ServiceName is the full name of the metrics service, derived from Kustomize\n\tServiceName string\n\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *ServiceMonitor) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = defaultOutputDir\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \"templates\", \"monitoring\", \"servicemonitor.yaml\")\n\t}\n\n\tchartName := f.ProjectName\n\tf.TemplateBody = fmt.Sprintf(serviceMonitorTemplate, chartName, chartName, chartName, chartName)\n\n\tf.IfExistsAction = machinery.OverwriteFile\n\n\treturn nil\n}\n\nconst serviceMonitorTemplate = `{{` + \"`\" + `{{- if .Values.prometheus.enable }}` + \"`\" + `}}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ \"{{ .Release.Service }}\" }}\n    app.kubernetes.io/name: {{ \"{{ include \\\"%s.name\\\" . }}\" }}\n    helm.sh/chart: {{ \"{{ .Chart.Name }}-{{ .Chart.Version | replace \\\"+\\\" \\\"_\\\" }}\" }}\n    app.kubernetes.io/instance: {{ \"{{ .Release.Name }}\" }}\n    control-plane: controller-manager\n  name: ` +\n\t`{{ \"{{ include \\\"%s.resourceName\\\" \" }}` +\n\t`{{ \"(dict \\\"suffix\\\" \\\"controller-manager-metrics-monitor\\\" \\\"context\\\" $) }}\" }}\n  namespace: {{ \"{{ .Release.Namespace }}\" }}\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        {{ \"{{- if .Values.certManager.enable }}\" }}\n        serverName: ` +\n\t`{{ \"{{ include \\\"%s.resourceName\\\" \" }}` +\n\t`{{ \"(dict \\\"suffix\\\" \\\"controller-manager-metrics-service\\\" \\\"context\\\" $) }}\" }}.` +\n\t`{{ \"{{ .Release.Namespace }}\" }}.svc\n        # Apply secure TLS configuration with cert-manager\n        insecureSkipVerify: false\n        ca:\n          secret:\n            name: metrics-server-cert\n            key: ca.crt\n        cert:\n          secret:\n            name: metrics-server-cert\n            key: tls.crt\n        keySecret:\n          name: metrics-server-cert\n          key: tls.key\n        {{ \"{{- else }}\" }}\n        # Development/Test mode (insecure configuration)\n        insecureSkipVerify: true\n        {{ \"{{- end }}\" }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ \"{{ include \\\"%s.name\\\" . }}\" }}\n      control-plane: controller-manager\n{{` + \"`\" + `{{- end }}` + \"`\" + `}}\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart-templates/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage charttemplates\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestChartTemplates(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Chart Templates Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/chart.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nconst defaultOutputDir = \"dist\"\n\nvar _ machinery.Template = &HelmChart{}\n\n// HelmChart scaffolds a file that defines the Helm chart structure\ntype HelmChart struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmChart) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = defaultOutputDir\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \"Chart.yaml\")\n\t}\n\n\tf.TemplateBody = helmChartTemplate\n\n\t// Chart.yaml is never overwritten as it contains user-managed version info\n\tf.IfExistsAction = machinery.SkipFile\n\n\treturn nil\n}\n\nconst helmChartTemplate = `apiVersion: v2\nname: {{ .ProjectName }}\ndescription: A Helm chart to distribute {{ .ProjectName }}\ntype: application\n\nversion: 0.1.0\nappVersion: \"0.1.0\"\n\nkeywords:\n  - kubernetes\n  - operator\n\nannotations:\n  kubebuilder.io/generated-by: kubebuilder\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/github/test_chart.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage github\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmChartCI{}\n\n// HelmChartCI scaffolds the GitHub Action for testing Helm charts\ntype HelmChartCI struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmChartCI) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\tf.Path = filepath.Join(\".github\", \"workflows\", \"test-chart.yml\")\n\t}\n\n\tf.TemplateBody = testChartTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst testChartTemplate = `name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare {{ .ProjectName }}\n        run: |\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n# TODO: Uncomment if cert-manager is enabled\n#      - name: Install cert-manager via Helm (wait for readiness)\n#        run: |\n#          helm repo add jetstack https://charts.jetstack.io\n#          helm repo update\n#          helm install cert-manager jetstack/cert-manager \\\n#            --namespace cert-manager \\\n#            --create-namespace \\\n#            --set crds.enabled=true \\\n#            --wait \\\n#            --timeout 300s\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Deploy manager via Helm\n        run: |\n          make helm-deploy IMG={{ .ProjectName }}:v0.1.0\n\n      - name: Check Helm release status\n        run: |\n          make helm-status\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/helmignore.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmIgnore{}\n\n// HelmIgnore scaffolds a file that defines the .helmignore for Helm packaging\ntype HelmIgnore struct {\n\tmachinery.TemplateMixin\n\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmIgnore) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = \"dist\"\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \".helmignore\")\n\t}\n\n\tf.TemplateBody = helmIgnoreTemplate\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\nconst helmIgnoreTemplate = `# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n`\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestTemplates(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Templates Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ machinery.Template = &HelmValuesBasic{}\n\n// HelmValuesBasic scaffolds a basic values.yaml based on detected features\ntype HelmValuesBasic struct {\n\tmachinery.TemplateMixin\n\tmachinery.ProjectNameMixin\n\n\t// DeploymentConfig stores extracted deployment configuration (env, resources, security contexts)\n\tDeploymentConfig map[string]any\n\t// OutputDir specifies the output directory for the chart\n\tOutputDir string\n\t// Force if true allows overwriting the scaffolded file\n\tForce bool\n\t// HasWebhooks is true when webhooks were found in the config\n\tHasWebhooks bool\n\t// HasMetrics is true when metrics service/monitor were found in the config\n\tHasMetrics bool\n}\n\n// SetTemplateDefaults implements machinery.Template\nfunc (f *HelmValuesBasic) SetTemplateDefaults() error {\n\tif f.Path == \"\" {\n\t\toutputDir := f.OutputDir\n\t\tif outputDir == \"\" {\n\t\t\toutputDir = \"dist\"\n\t\t}\n\t\tf.Path = filepath.Join(outputDir, \"chart\", \"values.yaml\")\n\t}\n\n\tf.TemplateBody = f.generateBasicValues()\n\n\tif f.Force {\n\t\tf.IfExistsAction = machinery.OverwriteFile\n\t} else {\n\t\tf.IfExistsAction = machinery.SkipFile\n\t}\n\n\treturn nil\n}\n\n// generateBasicValues creates a basic values.yaml based on detected features\nfunc (f *HelmValuesBasic) generateBasicValues() string {\n\tvar buf bytes.Buffer\n\n\t// Controller Manager configuration\n\timageRepo := \"controller\"\n\timageTag := \"latest\"\n\timagePullPolicy := \"IfNotPresent\"\n\tif f.DeploymentConfig != nil {\n\t\tif imgCfg, ok := f.DeploymentConfig[\"image\"].(map[string]any); ok {\n\t\t\tif repo, ok := imgCfg[\"repository\"].(string); ok && repo != \"\" {\n\t\t\t\timageRepo = repo\n\t\t\t}\n\t\t\tif tag, ok := imgCfg[\"tag\"].(string); ok && tag != \"\" {\n\t\t\t\timageTag = tag\n\t\t\t}\n\t\t\tif policy, ok := imgCfg[\"pullPolicy\"].(string); ok && policy != \"\" {\n\t\t\t\timagePullPolicy = policy\n\t\t\t}\n\t\t}\n\t}\n\n\tbuf.WriteString(fmt.Sprintf(`## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: %s\n    tag: %s\n    pullPolicy: %s\n\n`, imageRepo, imageTag, imagePullPolicy))\n\n\t// Add extracted deployment configuration\n\tf.addDeploymentConfig(&buf)\n\n\t// RBAC configuration\n\tbuf.WriteString(`## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n`)\n\n\t// CRD configuration\n\tbuf.WriteString(`## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n`)\n\n\t// Metrics configuration (enable if metrics artifacts detected in kustomize output)\n\tmetricsPort := 8443\n\tif f.DeploymentConfig != nil {\n\t\tif mp, ok := f.DeploymentConfig[\"metricsPort\"].(int); ok && mp > 0 {\n\t\t\tmetricsPort = mp\n\t\t}\n\t}\n\n\tif f.HasMetrics {\n\t\tbuf.WriteString(fmt.Sprintf(`## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: %d\n\n`, metricsPort))\n\t} else {\n\t\tbuf.WriteString(fmt.Sprintf(`## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: false\n  # Metrics server port\n  port: %d\n\n`, metricsPort))\n\t}\n\n\t// Cert-manager configuration (always present, enabled based on webhooks)\n\tif f.HasWebhooks {\n\t\tbuf.WriteString(`## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: true\n\n`)\n\t} else {\n\t\tbuf.WriteString(`## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: false\n\n`)\n\t}\n\n\t// Webhook configuration - only if webhooks are present\n\tif f.HasWebhooks {\n\t\twebhookPort := 9443\n\t\tif f.DeploymentConfig != nil {\n\t\t\tif wp, ok := f.DeploymentConfig[\"webhookPort\"].(int); ok && wp > 0 {\n\t\t\t\twebhookPort = wp\n\t\t\t}\n\t\t}\n\n\t\tbuf.WriteString(fmt.Sprintf(`## Webhook server configuration\n##\nwebhook:\n  enable: true\n  # Webhook server port\n  port: %d\n\n`, webhookPort))\n\t}\n\n\t// Prometheus configuration\n\tbuf.WriteString(`## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n`)\n\n\tbuf.WriteString(\"\\n\")\n\treturn buf.String()\n}\n\n// addDeploymentConfig adds extracted deployment configuration to the values\nfunc (f *HelmValuesBasic) addDeploymentConfig(buf *bytes.Buffer) {\n\tf.addArgsSection(buf)\n\n\tif f.DeploymentConfig == nil {\n\t\t// Add default sections with examples\n\t\tf.addDefaultDeploymentSections(buf)\n\t\treturn\n\t}\n\n\tf.addEnvSection(buf)\n\n\t// Add image pull secrets\n\tif imagePullSecrets, exists := f.DeploymentConfig[\"imagePullSecrets\"]; exists && imagePullSecrets != nil {\n\t\tbuf.WriteString(\"  ## Image pull secrets\\n\")\n\t\tbuf.WriteString(\"  ##\\n\")\n\t\tbuf.WriteString(\"  imagePullSecrets:\\n\")\n\t\tif imagePullSecretsYaml, err := yaml.Marshal(imagePullSecrets); err == nil {\n\t\t\tlines := bytes.SplitSeq(imagePullSecretsYaml, []byte(\"\\n\"))\n\t\t\tfor line := range lines {\n\t\t\t\tif len(line) > 0 {\n\t\t\t\t\tbuf.WriteString(\"    \")\n\t\t\t\t\tbuf.Write(line)\n\t\t\t\t\tbuf.WriteString(\"\\n\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tf.addDefaultImagePullSecrets(buf)\n\t}\n\n\t// Add podSecurityContext\n\tif podSecCtx, exists := f.DeploymentConfig[\"podSecurityContext\"]; exists && podSecCtx != nil {\n\t\tbuf.WriteString(\"  ## Pod-level security settings\\n\")\n\t\tbuf.WriteString(\"  ##\\n\")\n\t\tbuf.WriteString(\"  podSecurityContext:\\n\")\n\t\tif secYaml, err := yaml.Marshal(podSecCtx); err == nil {\n\t\t\tf.IndentYamlProperly(buf, secYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tf.addDefaultPodSecurityContext(buf)\n\t}\n\n\t// Add securityContext\n\tif secCtx, exists := f.DeploymentConfig[\"securityContext\"]; exists && secCtx != nil {\n\t\tbuf.WriteString(\"  ## Container-level security settings\\n\")\n\t\tbuf.WriteString(\"  ##\\n\")\n\t\tbuf.WriteString(\"  securityContext:\\n\")\n\t\tif secYaml, err := yaml.Marshal(secCtx); err == nil {\n\t\t\tf.IndentYamlProperly(buf, secYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tf.addDefaultSecurityContext(buf)\n\t}\n\n\t// Add resources\n\tif resources, exists := f.DeploymentConfig[\"resources\"]; exists && resources != nil {\n\t\tbuf.WriteString(\"  ## Resource limits and requests\\n\")\n\t\tbuf.WriteString(\"  ##\\n\")\n\t\tbuf.WriteString(\"  resources:\\n\")\n\t\tif resYaml, err := yaml.Marshal(resources); err == nil {\n\t\t\tf.IndentYamlProperly(buf, resYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tf.addDefaultResources(buf)\n\t}\n\n\tbuf.WriteString(\"  ## Manager pod's affinity\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tif affinity, exists := f.DeploymentConfig[\"podAffinity\"]; exists && affinity != nil {\n\t\tbuf.WriteString(\"  affinity:\\n\")\n\t\tif affYaml, err := yaml.Marshal(affinity); err == nil {\n\t\t\tf.IndentYamlProperly(buf, affYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tbuf.WriteString(\"  affinity: {}\\n\")\n\t\tbuf.WriteString(\"\\n\")\n\t}\n\n\tbuf.WriteString(\"  ## Manager pod's node selector\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tif nodeSelector, exists := f.DeploymentConfig[\"podNodeSelector\"]; exists && nodeSelector != nil {\n\t\tbuf.WriteString(\"  nodeSelector:\\n\")\n\t\tif nodYaml, err := yaml.Marshal(nodeSelector); err == nil {\n\t\t\tf.IndentYamlProperly(buf, nodYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tbuf.WriteString(\"  nodeSelector: {}\\n\")\n\t\tbuf.WriteString(\"\\n\")\n\t}\n\n\tbuf.WriteString(\"  ## Manager pod's tolerations\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tif tolerations, exists := f.DeploymentConfig[\"podTolerations\"]; exists && tolerations != nil {\n\t\tbuf.WriteString(\"  tolerations:\\n\")\n\t\tif tolYaml, err := yaml.Marshal(tolerations); err == nil {\n\t\t\tf.IndentYamlProperly(buf, tolYaml)\n\t\t}\n\t\tbuf.WriteString(\"\\n\")\n\t} else {\n\t\tbuf.WriteString(\"  tolerations: []\\n\")\n\t\tbuf.WriteString(\"\\n\")\n\t}\n}\n\nfunc (f *HelmValuesBasic) IndentYamlProperly(buf *bytes.Buffer, envYaml []byte) {\n\tlines := bytes.SplitSeq(envYaml, []byte(\"\\n\"))\n\tfor line := range lines {\n\t\tif len(line) > 0 {\n\t\t\tbuf.WriteString(\"    \")\n\t\t\tbuf.Write(line)\n\t\t\tbuf.WriteString(\"\\n\")\n\t\t}\n\t}\n}\n\n// addEnvSection writes env (list, same as master) and only adds envOverrides for CLI.\nfunc (f *HelmValuesBasic) addEnvSection(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Environment variables\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tif env, exists := f.DeploymentConfig[\"env\"]; exists && env != nil {\n\t\tif list, ok := env.([]any); ok && len(list) > 0 {\n\t\t\tbuf.WriteString(\"  env:\\n\")\n\t\t\tif envYaml, err := yaml.Marshal(list); err == nil {\n\t\t\t\tf.IndentYamlProperly(buf, envYaml)\n\t\t\t}\n\t\t} else {\n\t\t\tbuf.WriteString(\"  env: []\\n\")\n\t\t}\n\t} else {\n\t\tbuf.WriteString(\"  env: []\\n\")\n\t}\n\tbuf.WriteString(\"\\n\")\n\tbuf.WriteString(\"  ## Env overrides (--set manager.envOverrides.VAR=value)\\n\")\n\tbuf.WriteString(\"  ## Same name in env above: this value takes precedence.\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  envOverrides: {}\\n\")\n\tbuf.WriteString(\"\\n\")\n}\n\n// addDefaultDeploymentSections adds default sections when no deployment config is available\nfunc (f *HelmValuesBasic) addDefaultDeploymentSections(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Environment variables\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  env: []\\n\")\n\tbuf.WriteString(\"\\n\")\n\tbuf.WriteString(\"  ## Env overrides (--set manager.envOverrides.VAR=value)\\n\")\n\tbuf.WriteString(\"  ## Same name in env above: this value takes precedence.\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  envOverrides: {}\\n\")\n\tbuf.WriteString(\"\\n\")\n\n\tf.addDefaultImagePullSecrets(buf)\n\tf.addDefaultPodSecurityContext(buf)\n\tf.addDefaultSecurityContext(buf)\n\tf.addDefaultResources(buf)\n}\n\n// addArgsSection adds controller manager args section to the values file\nfunc (f *HelmValuesBasic) addArgsSection(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Arguments\\n  ##\\n\")\n\n\tif f.DeploymentConfig != nil {\n\t\tif args, exists := f.DeploymentConfig[\"args\"]; exists && args != nil {\n\t\t\tif argsYaml, err := yaml.Marshal(args); err == nil {\n\t\t\t\tif trimmed := strings.TrimSpace(string(argsYaml)); trimmed != \"\" && trimmed != \"[]\" {\n\t\t\t\t\tlines := bytes.Split(argsYaml, []byte(\"\\n\"))\n\t\t\t\t\tbuf.WriteString(\"  args:\\n\")\n\t\t\t\t\tfor _, line := range lines {\n\t\t\t\t\t\tif len(line) > 0 {\n\t\t\t\t\t\t\tbuf.WriteString(\"    \")\n\t\t\t\t\t\t\tbuf.Write(line)\n\t\t\t\t\t\t\tbuf.WriteString(\"\\n\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbuf.WriteString(\"\\n\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tbuf.WriteString(\"  args: []\\n\\n\")\n}\n\n// addDefaultImagePullSecrets adds default imagePullSecrets section\nfunc (f *HelmValuesBasic) addDefaultImagePullSecrets(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Image pull secrets\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  imagePullSecrets: []\\n\\n\")\n}\n\n// addDefaultPodSecurityContext adds default podSecurityContext section\nfunc (f *HelmValuesBasic) addDefaultPodSecurityContext(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Pod-level security settings\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  podSecurityContext: {}\\n\")\n\tbuf.WriteString(\"    # fsGroup: 2000\\n\\n\")\n}\n\n// addDefaultSecurityContext adds default securityContext section\nfunc (f *HelmValuesBasic) addDefaultSecurityContext(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Container-level security settings\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  securityContext: {}\\n\")\n\tbuf.WriteString(\"    # capabilities:\\n\")\n\tbuf.WriteString(\"    #   drop:\\n\")\n\tbuf.WriteString(\"    #   - ALL\\n\")\n\tbuf.WriteString(\"    # readOnlyRootFilesystem: true\\n\")\n\tbuf.WriteString(\"    # runAsNonRoot: true\\n\")\n\tbuf.WriteString(\"    # runAsUser: 1000\\n\\n\")\n}\n\n// addDefaultResources adds default resources section\nfunc (f *HelmValuesBasic) addDefaultResources(buf *bytes.Buffer) {\n\tbuf.WriteString(\"  ## Resource limits and requests\\n\")\n\tbuf.WriteString(\"  ##\\n\")\n\tbuf.WriteString(\"  resources: {}\\n\")\n\tbuf.WriteString(\"    # limits:\\n\")\n\tbuf.WriteString(\"    #   cpu: 100m\\n\")\n\tbuf.WriteString(\"    #   memory: 128Mi\\n\")\n\tbuf.WriteString(\"    # requests:\\n\")\n\tbuf.WriteString(\"    #   cpu: 100m\\n\")\n\tbuf.WriteString(\"    #   memory: 128Mi\\n\\n\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/values_basic_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\nvar _ = Describe(\"HelmValuesBasic\", func() {\n\tvar valuesTemplate *HelmValuesBasic\n\n\tContext(\"when project has webhooks\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tHasWebhooks:      true,\n\t\t\t\tDeploymentConfig: map[string]any{},\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should include certManager configuration\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\n\t\t\tExpect(content).To(ContainSubstring(\"certManager:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"enable: true\"))\n\t\t})\n\n\t\tIt(\"should include all basic sections\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\n\t\t\tExpect(content).To(ContainSubstring(\"manager:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"args: []\"))\n\t\t\tExpect(content).To(ContainSubstring(\"env: []\"))\n\t\t\tExpect(content).To(ContainSubstring(\"envOverrides: {}\"))\n\t\t\tExpect(content).To(ContainSubstring(\"metrics:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"prometheus:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"rbacHelpers:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"imagePullSecrets: []\"))\n\t\t})\n\n\t\tIt(\"should include env list and envOverrides for CLI\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(\"env: []\"))\n\t\t\tExpect(content).To(ContainSubstring(\"envOverrides: {}\"))\n\t\t\tExpect(content).To(ContainSubstring(\"--set manager.envOverrides.VAR=value\"))\n\t\t})\n\t})\n\n\tContext(\"when project has no webhooks\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tHasWebhooks:      false,\n\t\t\t\tDeploymentConfig: map[string]any{},\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should not include certManager configuration\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\n\t\t\tExpect(content).To(ContainSubstring(\"certManager:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"enable: false\"))\n\t\t})\n\n\t\tIt(\"should still include other basic sections\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\n\t\t\tExpect(content).To(ContainSubstring(\"manager:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"args: []\"))\n\t\t\tExpect(content).To(ContainSubstring(\"metrics:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"prometheus:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"rbacHelpers:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"imagePullSecrets: []\"))\n\t\t})\n\t})\n\n\tContext(\"template path and content\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tOutputDir: \"dist\",\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should have correct path\", func() {\n\t\t\tExpect(valuesTemplate.GetPath()).To(Equal(\"dist/chart/values.yaml\"))\n\t\t})\n\n\t\tIt(\"should implement Builder interface\", func() {\n\t\t\tvar builder machinery.Builder = valuesTemplate\n\t\t\tExpect(builder).NotTo(BeNil())\n\t\t})\n\n\t\tIt(\"should have correct file permissions\", func() {\n\t\t\tinfo := valuesTemplate.GetIfExistsAction()\n\t\t\tExpect(info).To(Equal(machinery.SkipFile))\n\t\t})\n\t})\n\n\tContext(\"with deployment configuration\", func() {\n\t\tBeforeEach(func() {\n\t\t\tdeploymentConfig := map[string]any{\n\t\t\t\t\"args\": []any{\n\t\t\t\t\t\"--leader-elect\",\n\t\t\t\t},\n\t\t\t\t\"env\": []any{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"name\":  \"TEST_ENV\",\n\t\t\t\t\t\t\"value\": \"test-value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"image\": map[string]any{\n\t\t\t\t\t\"repository\": \"example.com/custom-controller\",\n\t\t\t\t\t\"tag\":        \"v1.2.3\",\n\t\t\t\t\t\"pullPolicy\": \"Always\",\n\t\t\t\t},\n\t\t\t\t\"resources\": map[string]any{\n\t\t\t\t\t\"limits\": map[string]any{\n\t\t\t\t\t\t\"cpu\":    \"100m\",\n\t\t\t\t\t\t\"memory\": \"128Mi\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tHasWebhooks:      false,\n\t\t\t\tDeploymentConfig: deploymentConfig,\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should include deployment configuration\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(\"args:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"- --leader-elect\"))\n\t\t\tExpect(content).To(ContainSubstring(\"env:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"TEST_ENV\"))\n\t\t\tExpect(content).To(ContainSubstring(\"test-value\"))\n\t\t\tExpect(content).To(ContainSubstring(\"repository: example.com/custom-controller\"))\n\t\t\tExpect(content).To(ContainSubstring(\"tag: v1.2.3\"))\n\t\t\tExpect(content).To(ContainSubstring(\"pullPolicy: Always\"))\n\t\t\tExpect(content).To(ContainSubstring(\"resources:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"cpu: 100m\"))\n\t\t\tExpect(content).To(ContainSubstring(\"memory: 128Mi\"))\n\t\t\tExpect(content).To(ContainSubstring(\"affinity: {}\"))\n\t\t\tExpect(content).To(ContainSubstring(\"nodeSelector: {}\"))\n\t\t\tExpect(content).To(ContainSubstring(\"tolerations: []\"))\n\t\t})\n\t})\n\n\tContext(\"with nodeSelector, affinity and tolerations configuration\", func() {\n\t\tBeforeEach(func() {\n\t\t\tdeploymentConfig := map[string]any{\n\t\t\t\t\"podNodeSelector\": map[string]string{\n\t\t\t\t\t\"kubernetes.io/os\": \"linux\",\n\t\t\t\t},\n\t\t\t\t\"podTolerations\": []map[string]string{\n\t\t\t\t\t{\n\t\t\t\t\t\t\"key\":      \"key1\",\n\t\t\t\t\t\t\"operator\": \"Equal\",\n\t\t\t\t\t\t\"effect\":   \"NoSchedule\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"podAffinity\": map[string]any{\n\t\t\t\t\t\"nodeAffinity\": map[string]any{\n\t\t\t\t\t\t\"requiredDuringSchedulingIgnoredDuringExecution\": map[string]any{\n\t\t\t\t\t\t\t\"nodeSelectorTerms\": []any{\n\t\t\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\t\t\"matchExpressions\": []any{\n\t\t\t\t\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\t\t\t\t\"key\":      \"topology.kubernetes.io/zone\",\n\t\t\t\t\t\t\t\t\t\t\t\"operator\": \"In\",\n\t\t\t\t\t\t\t\t\t\t\t\"values\":   []string{\"antarctica-east1\", \"antarctica-east2\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tHasWebhooks:      false,\n\t\t\t\tDeploymentConfig: deploymentConfig,\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should include default values\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(`  ## Manager pod's node selector\n  ##\n  nodeSelector:\n    kubernetes.io/os: linux`))\n\n\t\t\tExpect(content).To(ContainSubstring(`  ## Manager pod's tolerations\n  ##\n  tolerations:\n    - effect: NoSchedule\n      key: key1\n      operator: Equal`))\n\n\t\t\tExpect(content).To(ContainSubstring(`  ## Manager pod's affinity\n  ##\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: topology.kubernetes.io/zone\n            operator: In\n            values:\n            - antarctica-east1\n            - antarctica-east2`))\n\t\t})\n\t})\n\n\tContext(\"with multiple imagePullSecrets\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tDeploymentConfig: map[string]any{\n\t\t\t\t\t\"imagePullSecrets\": []any{\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"name\": \"test-secret\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"name\": \"test-secret2\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-private-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should render multiple imagePullSecrets\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(\"imagePullSecrets:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"- name: test-secret\"))\n\t\t\tExpect(content).To(ContainSubstring(\"- name: test-secret2\"))\n\t\t})\n\t})\n\n\tContext(\"with complex env variables\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tDeploymentConfig: map[string]any{\n\t\t\t\t\t\"env\": []any{\n\t\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\t\"name\": \"POD_NAMESPACE\",\n\t\t\t\t\t\t\t\"valueFrom\": map[string]any{\n\t\t\t\t\t\t\t\t\"fieldRef\": map[string]any{\n\t\t\t\t\t\t\t\t\t\"fieldPath\": \"metadata.namespace\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should render nested env configuration\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(\"env:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"POD_NAMESPACE\"))\n\t\t\tExpect(content).To(ContainSubstring(\"valueFrom:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"fieldRef:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"fieldPath: metadata.namespace\"))\n\t\t})\n\t})\n\n\tContext(\"rbacHelpers configuration\", func() {\n\t\tBeforeEach(func() {\n\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\tHasWebhooks: false,\n\t\t\t}\n\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should have rbacHelpers disabled by default\", func() {\n\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\tExpect(content).To(ContainSubstring(\"rbacHelpers:\"))\n\t\t\tExpect(content).To(ContainSubstring(\"enable: false\"))\n\t\t})\n\t})\n\n\tContext(\"Port configuration\", func() {\n\t\tContext(\"with default ports\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\t\tHasWebhooks:      true,\n\t\t\t\t\tHasMetrics:       true,\n\t\t\t\t\tDeploymentConfig: map[string]any{},\n\t\t\t\t}\n\t\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should include default webhook port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"webhook:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"enable: true\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 9443\"))\n\t\t\t})\n\n\t\t\tIt(\"should include certManager enabled\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"certManager:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"enable: true\"))\n\t\t\t})\n\n\t\t\tIt(\"should include default metrics port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"metrics:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"enable: true\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 8443\"))\n\t\t\t})\n\n\t\t\tIt(\"should not expose health port in values\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"healthPort\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"with custom ports extracted from deployment\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tdeploymentConfig := map[string]any{\n\t\t\t\t\t\"webhookPort\": 9444,\n\t\t\t\t\t\"metricsPort\": 9090,\n\t\t\t\t}\n\n\t\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\t\tHasWebhooks:      true,\n\t\t\t\t\tHasMetrics:       true,\n\t\t\t\t\tDeploymentConfig: deploymentConfig,\n\t\t\t\t}\n\t\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should use custom webhook port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"webhook:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 9444\"))\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"port: 9443\"))\n\t\t\t})\n\n\t\t\tIt(\"should use custom metrics port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"metrics:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 9090\"))\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"port: 8443\"))\n\t\t\t})\n\n\t\t\tIt(\"should not expose health port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"healthPort\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"with partial custom ports\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tdeploymentConfig := map[string]any{\n\t\t\t\t\t\"metricsPort\": 9090,\n\t\t\t\t\t// webhookPort not provided - should use default\n\t\t\t\t}\n\n\t\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\t\tHasWebhooks:      true,\n\t\t\t\t\tHasMetrics:       true,\n\t\t\t\t\tDeploymentConfig: deploymentConfig,\n\t\t\t\t}\n\t\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should use custom metrics port and default webhook port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 9090\")) // custom metrics\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 9443\")) // default webhook\n\t\t\t})\n\n\t\t\tIt(\"should not expose health port\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"healthPort\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"with no webhooks but with metrics\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\t\tHasWebhooks:      false,\n\t\t\t\t\tHasMetrics:       true,\n\t\t\t\t\tDeploymentConfig: map[string]any{},\n\t\t\t\t}\n\t\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should not include webhook configuration\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"webhook:\"))\n\t\t\t})\n\n\t\t\tIt(\"should include metrics port configuration\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tExpect(content).To(ContainSubstring(\"metrics:\"))\n\t\t\t\tExpect(content).To(ContainSubstring(\"port: 8443\"))\n\t\t\t\tExpect(content).NotTo(ContainSubstring(\"healthPort\"))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"port configuration structure\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tvaluesTemplate = &HelmValuesBasic{\n\t\t\t\t\tHasWebhooks:      true,\n\t\t\t\t\tHasMetrics:       true,\n\t\t\t\t\tDeploymentConfig: map[string]any{},\n\t\t\t\t}\n\t\t\t\tvaluesTemplate.InjectProjectName(\"test-project\")\n\t\t\t\terr := valuesTemplate.SetTemplateDefaults()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should have ports under webhook section\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tlines := strings.Split(content, \"\\n\")\n\n\t\t\t\tvar webhookLine, portLine int\n\t\t\t\tfor i, line := range lines {\n\t\t\t\t\tif strings.Contains(line, \"webhook:\") && !strings.Contains(line, \"#\") {\n\t\t\t\t\t\twebhookLine = i\n\t\t\t\t\t}\n\t\t\t\t\tif webhookLine > 0 && i > webhookLine && strings.Contains(line, \"port:\") &&\n\t\t\t\t\t\tstrings.Contains(line, \"9443\") {\n\t\t\t\t\t\tportLine = i\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tExpect(webhookLine).To(BeNumerically(\">\", 0))\n\t\t\t\tExpect(portLine).To(BeNumerically(\">\", webhookLine))\n\t\t\t\tExpect(portLine - webhookLine).To(BeNumerically(\"<=\", 3))\n\t\t\t})\n\n\t\t\tIt(\"should have port under metrics section\", func() {\n\t\t\t\tcontent := valuesTemplate.GetBody()\n\t\t\t\tlines := strings.Split(content, \"\\n\")\n\n\t\t\t\tvar metricsLine, portLine int\n\t\t\t\tfor i, line := range lines {\n\t\t\t\t\tif strings.Contains(line, \"metrics:\") && !strings.Contains(line, \"#\") {\n\t\t\t\t\t\tmetricsLine = i\n\t\t\t\t\t}\n\t\t\t\t\tif metricsLine > 0 && i > metricsLine {\n\t\t\t\t\t\tif strings.Contains(line, \"port:\") && strings.Contains(line, \"8443\") {\n\t\t\t\t\t\t\tportLine = i\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tExpect(metricsLine).To(BeNumerically(\">\", 0))\n\t\t\t\tExpect(portLine).To(BeNumerically(\">\", metricsLine))\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/scaffolds/suite_test.go",
    "content": "//go:build integration\n\n/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage scaffolds\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestScaffolds(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Scaffolds Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/optional/helm/v2alpha/suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestHelmV2AlphaPlugin(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Helm v2-alpha Plugin Suite\")\n}\n"
  },
  {
    "path": "pkg/plugins/scaffolder.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage plugins\n\nimport (\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/machinery\"\n)\n\n// Scaffolder interface creates files to set up a controller manager\ntype Scaffolder interface {\n\tInjectFS(machinery.Filesystem)\n\t// Scaffold performs the scaffolding\n\tScaffold() error\n}\n"
  },
  {
    "path": "roadmap/README.md",
    "content": "# Kubebuilder Roadmaps\n\n**Welcome to the Kubebuilder Roadmaps directory!**\n\nThis space is dedicated to housing the strategic roadmaps for the\nKubebuilder project, organized by year. Each document within this repository\noutlines the key initiatives, objectives, and goals for Kubebuilder, reflecting our\ncommitment to enhancing the development experience within the Kubernetes ecosystem.\n\nBelow, you will find links to the roadmap document for each year. These documents provide insights into the\nspecific objectives set for the project during that time, the motivation behind each goal, and the progress\nmade towards achieving them:\n\n- [Roadmap 2024](roadmap_2024.md)\n- [Roadmap 2025](roadmap_2025.md)\n- [Roadmap 2026](roadmap_2026.md)\n\n## :point_right: New plugins/RFEs to provide integrations within other Projects\n\nAs Kubebuilder evolves, we prioritize a focused project scope and minimal reliance on third-party dependencies,\nconcentrating on features that bring the most value to our community.\n\nWhile recognizing the need for flexibility, we opt not to directly support third-party project integrations.\nInstead, we've enhanced Kubebuilder as a library, enabling any project to create compatible plugins.\nThis approach delegates maintenance to those with the deepest understanding of their projects, fostering higher\nquality and community contributions.\n\nWe're here to support you in developing your own Kubebuilder plugins.\nFor guidance on [Creating Your own plugins](https://kubebuilder.io/plugins/creating-plugins).\n\nThis strategy empowers our users and contributors to innovate,\nkeeping Kubebuilder streamlined and focused on essential Kubernetes development functionalities.\n\n**Therefore, our primary objective remains to offer a CLI tool that assists users in developing\nsolutions for deployment and distribution on Kubernetes clusters using Golang.\nWe aim to simplify the complexities involved and speed up the development process,\nthereby lowering the learning curve.**\n\n## :steam_locomotive: Contributing\n\nYour input and contributions are what make Kubebuilder a continually\nevolving and improving project. We encourage the community to participate in discussions,\nprovide feedback on the roadmaps, and contribute to the development efforts.\n\nIf you have suggestions for future objectives or want to get involved\nin current initiatives, please refer to our [contributing guidelines](./../CONTRIBUTING.md)\nor reach out to the project maintainers. Please, feel free either\nto raise new issues and/or Pull Requests against this repository with your\nsuggestions.\n\n## :loudspeaker: Stay Tuned\n\nFor the latest updates, discussions, and contributions to the Kubebuilder project,\nplease join our community channels and forums. Your involvement is crucial for the\nsustained growth and success of Kubebuilder.\n\n**:tada: Thank you for being a part of the Kubebuilder journey.**\n\nTogether, we are building the future of Kubernetes development.\n\n## Template for roadmap items\n\n```markdown\n### [Goal Title]\n\n**Status:** [Status Emoji] [Short Status Update]\n\n**Objective:** [Brief description of the objective]\n\n**Context:** [Optional - Any relevant background or broader context]\n\n**Motivations:** [Optional - If applicable]\n- [Key motivation 1]\n- [Key motivation 2]\n\n**Proposed Solutions:** [Optional - If applicable]\n- [Solution 1]\n- [Solution 2]\n- [More as needed]\n\n**References:** [Optional - Links to discussions, PRs, issues, etc.]\n- [Reference 1 with URL]\n- [Reference 2 with URL]\n- [More as needed]\n```\n"
  },
  {
    "path": "roadmap/roadmap_2024.md",
    "content": "# Kubebuilder Project Roadmap 2024\n\n### Updating Scaffolding to Align with the Latest changes of controller-runtime\n\n**Status:** ✅ Complete (Changes available from release `4.3.0`)\n\n**Objective:** Update Kubebuilder's controller scaffolding to align with the latest changes\nin controller-runtime, focusing on compatibility and addressing recent updates and deprecations\nmainly related to webhooks.\n\n**Context:** Kubebuilder's plugin system is designed for stability, yet it depends on controller-runtime,\nwhich is evolving rapidly with versions still under 1.0.0. Notable changes and deprecations,\nespecially around webhooks, necessitate Kubebuilder's alignment with the latest practices\nand functionalities of controller-runtime. We need update the Kubebuilder scaffolding,\nsamples, and documentation.\n\n**References:**\n- [Issue - Deprecations in Controller-Runtime and Impact on Webhooks](https://github.com/kubernetes-sigs/kubebuilder/issues/3721) - An issue detailing the deprecations in controller-runtime that affect Kubebuilder's approach to webhooks.\n- [PR - Update to Align with Latest Controller-Runtime Webhook Interface](https://github.com/kubernetes-sigs/kubebuilder/pull/3399) - A pull request aimed at updating Kubebuilder to match controller-runtime's latest webhook interface.\n- [PR - Enhancements to Controller Scaffolding for Upcoming Controller-Runtime Changes](https://github.com/kubernetes-sigs/kubebuilder/pull/3723) - A pull request proposing enhancements to Kubebuilder's controller scaffolding in anticipation of upcoming changes in controller-runtime.\n\n\n#### (New Optional Plugin) Helm Chart Packaging\n\n**Status:** ✅ Complete ( Initial version merged https://github.com/kubernetes-sigs/kubebuilder/pull/4227 - further improvements and contributions are welcome)\n\n**Objective:** We aim to introduce a new plugin for Kubebuilder that packages projects as Helm charts,\nfacilitating easier distribution and integration of solutions within the Kubernetes ecosystem. For details on this proposal and how to contribute,\nsee [GitHub Pull Request #3632](https://github.com/kubernetes-sigs/kubebuilder/pull/3632).\n\n**Motivation:** The growth of the Kubernetes ecosystem underscores the need for flexible and\naccessible distribution methods. A Helm chart packaging plugin would simplify the distribution of the solutions\nand allow easy integrations with common applications used by administrators.\n\n---\n### Transition from Google Cloud Platform (GCP) to build and promote binaries and images\n\n**Status:**\n- **Kubebuilder CLI**: :white_check_mark: Complete. It has been built using Go releaser. [More info](./../build/.goreleaser.yml)\n- **kube-rbac-proxy Images:**  :white_check_mark: Complete. ([More info](https://github.com/kubernetes-sigs/kubebuilder/discussions/3907))\n- **EnvTest binaries:** :white_check_mark: Complete Controller-Runtime maintainers are working in a solution to build them out and take the ownership over this one. More info:\n  - https://kubernetes.slack.com/archives/C02MRBMN00Z/p1712457941924299\n  - https://kubernetes.slack.com/archives/CCK68P2Q2/p1713174342482079\n  - Also, see the PR: https://github.com/kubernetes-sigs/controller-runtime/pull/2811\n  - It will be available from the next release v0.19.\n- **PR Check image:**  🙌 Seeking Contributions to do the required changes - See that the images used to check the PR titles are also build and promoted by the Kubebuilder project in GCP but are from the project: https://github.com/kubernetes-sigs/kubebuilder-release-tools. The plan in this case is to use the e2e shared infrastructure. [More info](https://github.com/kubernetes/k8s.io/issues/2647#issuecomment-2111182864)\n\n**Objective:** Shift Kubernetes (k8s) project infrastructure from GCP to shared infrastructures.\nFurthermore, move from the registry `k8s.gcr.io` to `registry.k8s.io`.\n\n**Motivation:** The initiative to move away from GCP aligns with the broader k8s project's\ngoal of utilizing shared infrastructures. This transition is crucial for ensuring the availability\nof the artifacts in the long run and aligning compliance with other projects under the kubernetes-sig org.\n[Issue #2647](https://github.com/kubernetes/k8s.io/issues/2647) provides more details on the move.\n\n**Context:** Currently, Google Cloud is used only for:\n\n- **Rebuild and provide the images for kube-rbac-proxy:**\n\nA particular challenge has been the necessity to rebuild images for the\n[kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy), which is in the process of being\ndonated to kubernetes-sig. This transition was expected to eliminate the need for\ncontinuous re-tagging and rebuilding of its images to ensure their availability to users.\nThe configuration for building these images is outlined\n[here](https://github.com/kubernetes-sigs/kubebuilder/blob/master/RELEASE.md#to-build-the-kube-rbac-proxy-images).\n\n- **Build and Promote EnvTest binaries**:\n\nThe development of Kubebuilder Tools and EnvTest binaries,\nessential for controller tests, represents another area reliant on k8s binaries\ntraditionally built within GCP environments. Our documentation on building these artifacts is\navailable [here](https://github.com/kubernetes-sigs/kubebuilder/blob/master/RELEASE.md#to-build-the-kubebuilder-tools-artifacts-required-to-use-env-test).\n\n**We encourage the Kubebuilder community to participate in this discussion, offering feedback and contributing ideas\nto refine these proposals. Your involvement is crucial in shaping the future of secure and efficient project scaffolding in Kubebuilder.**\n\n---\n### kube-rbac-proxy's Role in Default Scaffold\n\n**Status:** :white_check_mark: Complete\n\n- **Resolution**: The usage of kube-rbac-proxy has been discontinued from the default scaffold. We plan to provide other helpers to protect the metrics endpoint. Furthermore, once the project is accepted under kubernetes-sig or kubernetes-auth, we may contribute to its maintainer in developing an external plugin for use with projects built with Kubebuilder.\n   - **Proposal**: [https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/discontinue_usage_of_kube_rbac_proxy.md](https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/discontinue_usage_of_kube_rbac_proxy.md)\n   - **PR**: [https://github.com/kubernetes-sigs/kubebuilder/pull/3899](https://github.com/kubernetes-sigs/kubebuilder/pull/3899)\n   - **Communication**: [https://github.com/kubernetes-sigs/kubebuilder/discussions/3907](https://github.com/kubernetes-sigs/kubebuilder/discussions/3907)\n\n**Objective:** Evaluate potential modifications or the exclusion of [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy)\nfrom the default Kubebuilder scaffold in response to deprecations and evolving user requirements.\n\n**Context:** [kube-rbac-proxy](https://github.com/brancz/kube-rbac-proxy) , a key component for securing Kubebuilder-generated projects,\nfaces significant deprecations that impact automatic certificate generation.\nFor more insights into these challenges, see [Issue #3524](https://github.com/kubernetes-sigs/kubebuilder/issues/3524).\n\nThis situation necessitates a reevaluation of its inclusion and potentially prompts users to\nadopt alternatives like cert-manager by default. Additionally, the requirement to manually rebuild\n[kube-rbac-proxy images—due](https://github.com/kubernetes-sigs/kubebuilder/blob/master/RELEASE.md#to-build-the-kube-rbac-proxy-images)\nto its external status from Kubernetes-SIG—places a considerable maintenance\nburden on Kubebuilder maintainers.\n\n**Motivations:**\n- Address kube-rbac-proxy breaking changes/deprecations.\n  - For further information: [Issue #3524 - kube-rbac-proxy warn about deprecation and future breaking changes](https://github.com/kubernetes-sigs/kubebuilder/issues/3524)\n- Feedback from the community has highlighted a preference for cert-manager's default integration, aiming security with Prometheus and metrics.\n  - More info: [GitHub Issue #3524 - Improve scaffolding of ServiceMonitor](https://github.com/kubernetes-sigs/kubebuilder/issues/3657)\n- Desire for kube-rbac-proxy to be optional, citing its prescriptive nature.\n  - See: [Issue #3482 - The kube-rbac-proxy is too opinionated to be opt-out.](https://github.com/kubernetes-sigs/kubebuilder/issues/3482)\n- Reduce the maintainability effort to generate the images used by Kubebuilder projects and dependency within third-party solutions.\n  - Related issues:\n    - [Issue #1885 - use a NetworkPolicy instead of kube-rbac-proxy](https://github.com/kubernetes-sigs/kubebuilder/issues/1885)\n    - [Issue #3230 - Migrate away from google.com gcp project kubebuilder](https://github.com/kubernetes-sigs/kubebuilder/issues/3230)\n\n---\n### Providing Helpers for Project Distribution\n\n#### Distribution via Kustomize\n\n**Status:** :white_check_mark: Complete\n\n- **Resolution**: As of release ([v3.14.0](https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v3.14.0)), Kubebuilder includes enhanced support for project distribution. Users can now scaffold projects with a `build-installer` makefile target. This improvement enables the straightforward deployment of solutions directly to Kubernetes clusters. Users can deploy their projects using commands like:\n\n```shell\nkubectl apply -f https://raw.githubusercontent.com/<org>/my-project/<tag or branch>/dist/install.yaml\n```\nThis enhancement streamlines the process of getting Kubebuilder projects running on clusters, providing a seamless deployment experience.\n\n---\n### **(Major Release for Kubebuilder CLI 4.x)** Removing Deprecated Plugins for Enhanced Maintainability and User Experience\n\n**Status:** : ✅ Complete - Release was done\n  - **Remove Deprecations**:https://github.com/kubernetes-sigs/kubebuilder/issues/3603\n  - **Bump Module**: https://github.com/kubernetes-sigs/kubebuilder/pull/3924\n\n**Objective:** To remove all deprecated plugins from Kubebuilder to improve project maintainability and\nenhance user experience. This initiative also includes updating the project documentation to provide clear\nand concise information, eliminating any confusion for users. **More Info:** [GitHub Discussion #3622](https://github.com/kubernetes-sigs/kubebuilder/discussions/3622)\n\n**Motivation:** By focusing on removing deprecated plugins—specifically, versions or kinds that can no\nlonger be supported—we aim to streamline the development process and ensure a higher quality user experience.\nClear and updated documentation will further assist in making development workflows more efficient and less prone to errors.\n\n"
  },
  {
    "path": "roadmap/roadmap_2025.md",
    "content": "# Kubebuilder Project Roadmap 2025\n\n## Ensure Webhook Implementation Stability and Enhance User Experience\n\n**Status:** Partially Complete\n\n### Objective\nEnhance the webhooks implementation and user experience.\n\n### Context\nThe current implementations for webhook conversion and defaulting are stable and tested through basic end-to-end (E2E) workflows.\nHowever, webhook conversion is incomplete, and several bugs need to be addressed. Additionally, the user experience\nis hindered by limitations such as the inability to add additional webhooks for same API without using the force flag\nand losing their existing customizations on top.\n\n### Goals and Needs\n- **CA Injection**: ✅ Complete (Changes available from release `4.4.0`) Ensure that CA injection for conversion webhooks is limited to the relevant Custom Resource (CR) conversions.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4285)\n  - [Pull Request](https://github.com/kubernetes-sigs/kubebuilder/pull/4282)\n\n- **Scaffolding Multiple Webhooks**: Allow adding additional webhooks without requiring forced re-scaffolding.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4146)\n\n- **Hub and Spoke Model**: ✅ Complete (Changes available from release `4.4.0`) Integrate a hub-and-spoke model for conversion webhooks to streamline implementation.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/2589)\n  - [Pull Request](https://github.com/kubernetes-sigs/kubebuilder/pull/4254)\n\n- **Comprehensive E2E Testing**: ✅ Complete ([Example](https://github.com/kubernetes-sigs/kubebuilder/blob/v4.7.1/testdata/project-v4-with-plugins/test/e2e/e2e_test.go#L284-L296)) Expand end-to-end tests for conversion webhooks to validate not only CA injection but also the conversion process itself.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4297)\n\n- **E2E Test Scaffolding**: (✅ Complete) Improve the E2E test scaffolds under `test/e2e` to validate conversion behavior beyond CA injection for conversion webhooks.\n\n- **Enhanced Multiversion Tutorial**: (✅ Complete) Add E2E tests for conversion webhooks in the multiversion tutorial to support comprehensive user guidance.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4255)\n\n---\n# Roadmap Document\n\n## Enhance the Helm Chart Plugin\n\n**Status:** ✅ Complete (Released in Kubebuilder v4.10.0, introduced `helm/v2-alpha` plugin which supersedes the previous version and addressed the community feedback.)\n\n### Context\nA new plugin to help users scaffold a Helm chart to distribute their solutions is implemented as an experimental\nfeature on the `master` branch and is currently under development. It's initial version will be released in\nthe next major version of Kubebuilder.\n\n### Objective\nThe objective of this effort is to ensure that the Helm chart plugin addresses user needs effectively while\nproviding a seamless and intuitive experience.\n\n### Goals\n- Prevent exposure of webhooks data in the Helm chart values.\n- Determine whether and how to include sample files and CR configurations in the Helm chart.\n- Enable users to specify the path where the Helm chart will be scaffolded.\n\n### References\n- [Milestone Helm](https://github.com/kubernetes-sigs/kubebuilder/milestone/39)\n- [Code Implementation](https://github.com/kubernetes-sigs/kubebuilder/tree/master/pkg/plugins/optional/helm)\n- [Sample Under Testdata](https://github.com/kubernetes-sigs/kubebuilder/tree/master/testdata/project-v4-with-plugins/dist/chart)\n\n---\n\n## Align Tutorials and Samples with Best Practices Proposed by DeployImage Plugin\n\n**Status:** ✅ Complete\n\n### Context\nThe existing tutorials lack consistency with best practices and the layout proposed by the DeployImage plugin.\n\n### Objective\nAlign tutorials and sample projects with best practices to improve quality and usability.\n\n### Goals\n- **Controller Logic Consistency**: Standardize tutorial controller logic to match the DeployImage plugin’s scaffolded controller, including conditions, finalizers, and status updates.\n\n- **Conditional Status in CronJob Spec**: Incorporate conditional status handling in the CronJob spec to reflect best practices.\n\n- **Test Logic Consistency**: Ensure tutorial test logic mirrors the tests scaffolded by the DeployImage plugin, adapting as needed for specific cases.\n\n---\n\n## Provide Solutions to Keep Users Updated with the Latest Changes\n\n**Status:** (✅ feature complete from 4.8.0 )\n- Proposal: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/update_action.md\n- `kubebuilder alpha update` command implemented. More info: https://book.kubebuilder.io/reference/commands/alpha_update\n- AutoUpdate Plugin implemented as v1-alpha. More info: https://book.kubebuilder.io/plugins/available/autoupdate-v1-alpha\n\n### Context\nKubebuilder currently offers a \"Help to Upgrade\" feature via the `kubebuilder alpha generate` command, but applying updates requires significant manual effort.\n\n### Objective\nDevelop an opt-in mechanism to notify users and automate updates, reducing manual effort and ensuring alignment with the latest Kubebuilder versions.\n\n### Goals\n- Facilitate keeping repositories updated with minimal manual intervention.\n- Provide automated notifications and updates inspired by Dependabot.\n- Maintain compatibility with new Kubebuilder features, best practices, and bug fixes.\n\n---\n"
  },
  {
    "path": "roadmap/roadmap_2026.md",
    "content": "# Kubebuilder Project Roadmap 2026\n\nThe main goals for 2026 are to promote adoption of the latest Kubebuilder versions and update automation, align tutorials and samples with best practices proposed by the [DeployImage plugin](https://kubebuilder.io/plugins/available/deploy-image-plugin-v1-alpha), improve documentation quality and consistency, explore how we can better leverage AI capabilities, and strengthen Kubebuilder as an API and plugin framework to encourage the creation and adoption of external plugins that extend and integrate with Kubebuilder.\n\n## Promote and encourage adoption of the latest Kubebuilder versions and automation mechanisms to stay updated\n\n**Status:** WIP\n\n### Context\nIn 2025, we introduced the `kubebuilder alpha update` command and the AutoUpdate plugin to help users stay current with the latest Kubebuilder changes.\nHowever, adoption of these features has been limited because many users are not aware of them or cannot update their projects easily to\nthe latest Kubebuilder versions and take advantage of these automation mechanisms.\n\n### Objective\nEnsure more projects use the latest Kubebuilder versions and adopt automation mechanisms to stay updated.\n\n### Goals\n- **Migration Documentation**:\n  - (Status Done but looking for collaboration and follow up) Simplify and enhance migration documentation to guide users through updating their projects to the latest Kubebuilder versions. Highlight the available automation mechanisms and provide a generic guide to migrate from any version to the latest manually, so users can then adopt the automation mechanisms going forward.\n\n- **Create Campaign to Promote Update Features**:\n  - (Status TODO) Launch a campaign to raise awareness about the `kubebuilder alpha update` command and the AutoUpdate plugin, highlighting their benefits and encouraging adoption among users.\n    - Similar to past campaigns where we created issues for public repos to help users become aware of critical changes (e.g., Kubernetes API deprecation from `v1beta1` to `v1`, and the migration away from `gcr.io/kubebuilder/kube-rbac-proxy`). See [discussion #3907](https://github.com/kubernetes-sigs/kubebuilder/discussions/3907).\n    - Note: before running this campaign, ensure migration documentation is in place to support users through the update process.\n    - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/5291)\n---\n\n## Enhance Webhooks CLI User Experience to support additional Webhook scaffolding without requiring `--force`\n\n**Status:** TODO\n\n### Objective\nEnhance the webhooks implementation and user experience.\n\n### Context\nCurrently, users can scaffold webhooks, but if they want to add additional webhook types for the same API they need to use `--force`.\nThis may overwrite existing customizations. The goal is to support iterative workflows where users can scaffold webhook type A and later add webhook type B\nwithout requiring forced re-scaffolding. The `--force` flag should remain available.\n\n### Goals and Needs\n- **Scaffolding Multiple Webhooks**: Allow adding additional webhook types for the same API without requiring forced re-scaffolding.\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4146)\n\n---\n\n## External plugins examples improvement and promotion\n\n**Status:** TODO\n\n### Objective\nEnhance the examples to demonstrate usage of external plugins for end users and encourage the usage of Kubebuilder as an API and plugin framework.\nHelp projects create their own plugins to extend and integrate with Kubebuilder.\n\n### Context\nKubebuilder supports external plugins, but we need clearer, maintained examples that show how to build, distribute, and use them in real projects.\n\n### Goals and Needs\n- **Make sampleexternalplugin a Valid Reference Implementation**\n  - [GitHub Issue](https://github.com/kubernetes-sigs/kubebuilder/issues/4146)\n"
  },
  {
    "path": "test/check-docs-only.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# This script runs goreleaser using the build/.goreleaser.yml config.\n# While it can be run locally, it is intended to be run by cloudbuild\n# in the goreleaser/goreleaser image.\n\nset -e\n\n# If running in Github actions: this should be set to \"github.base_ref\".\n: ${1?\"the first argument must be set to a commit-ish reference\"}\n\n# Patterns to ignore.\ndeclare -a DOC_PATTERNS\nDOC_PATTERNS=(\n  \"(\\.md)\"\n  \"(\\.MD)\"\n  \"(\\.png)\"\n  \"(\\.pdf)\"\n  \"(netlify\\.toml)\"\n  \"(OWNERS)\"\n  \"(OWNERS_ALIASES)\"\n  \"(LICENSE)\"\n  \"(docs/)\"\n)\n\nif ! git diff --name-only $1 | grep -qvE \"$(IFS=\"|\"; echo \"${DOC_PATTERNS[*]}\")\"; then\n  echo \"true\"\n  exit 0\nfi\n"
  },
  {
    "path": "test/check-license.sh",
    "content": "#!/bin/bash\n\n# Copyright 2021 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nsource $(dirname \"$0\")/common.sh\n\necho \"Checking for license header...\"\n#TODO: See if we can improve the Bollerplate logic for the Hack/License to allow scaffold the licenses\n#using the comment prefix # for yaml files.\nallfiles=$(listFiles | grep -v -e './internal/bindata/...' -e '.devcontainer/post-install.sh' -e '.github/*')\nlicRes=\"\"\nfor file in $allfiles; do\n  if [[ -f \"$file\" && \"$(file --mime-type -b \"$file\")\" == text/* ]]; then\n    # Read the first few lines but skip build tags for Go files\n    # Strip up to 3 lines starting with //go:build or // +build\n    stripped=$(head -n 30 \"$file\" \\\n      | sed '/^\\/\\/go:build\\|^\\/\\/ +build/d' \\\n      | sed '/^\\s*$/d' \\\n      | head -n 10)\n    if ! echo \"$stripped\" | grep -Eq \"(Copyright|generated|GENERATED|Licensed)\" ; then\n      licRes=\"${licRes}\\n  ${file}\"\n    fi\n  fi\ndone\nif [ -n \"${licRes}\" ]; then\n  echo -e \"license header checking failed:\\n${licRes}\"\n  exit 255\nfi\n"
  },
  {
    "path": "test/check_spaces.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2024 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfunction validate_docs_trailing_spaces {\n  if find . -type f -name \"*.md\" -exec grep -Hn '[[:space:]]$' {} +; then\n      echo \"Trailing spaces were found in docs files\"\n      exit 1\n  fi\n\n}\n\nvalidate_docs_trailing_spaces\n"
  },
  {
    "path": "test/common.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Not every exact cluster version has an equal tools version, and visa versa.\n# This function returns the exact tools version for a k8s version based on its minor.\nfunction convert_to_tools_ver {\n  local k8s_ver=${1:?\"k8s version must be set to arg 1\"}\n  local maj_min=$(echo $k8s_ver | grep -oE '^[0-9]+\\.[0-9]+')\n  case $maj_min in\n  # 1.14-1.19 work with the 1.19 server bins and kubectl.\n  \"1.14\"|\"1.15\"|\"1.16\"|\"1.17\"|\"1.18\"|\"1.19\") echo \"1.19.2\";;\n  # Tests in 1.20 and 1.21 with their counterpart version's apiserver.\n  \"1.20\"|\"1.21\") echo \"1.21.5\";;\n  \"1.22\") echo \"1.22.1\";;\n  \"1.23\") echo \"1.23.3\";;\n  \"1.24\") echo \"1.24.1\";;\n  \"1.25\") echo \"1.25.0\";;\n  \"1.26\") echo \"1.26.0\";;\n  \"1.27\") echo \"1.27.1\";;\n  \"1.28\") echo \"1.28.3\";;\n  \"1.29\") echo \"1.29.0\";;\n  \"1.30\") echo \"1.30.0\";;\n  \"1.31\") echo \"1.31.0\";;\n  \"1.32\") echo \"1.32.0\";;\n  \"1.33\") echo \"1.33.0\";;\n  \"1.34\") echo \"1.34.0\";;\n  \"1.35\") echo \"1.35.0\";;\n  *)\n    echo \"k8s version $k8s_ver not supported\"\n    exit 1\n  esac\n}\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# Force a static development version for ALL scripts that source common.sh.\n# This ensures PROJECT files in both /testdata and /docs remain consistent.\nexport KUBEBUILDER_TEST_VERSION=\"(devel)\"\n\n# Enable tracing in this script off by setting the TRACE variable in your\n# environment to any value:\n#\n# $ TRACE=1 test.sh\nTRACE=${TRACE:-\"\"}\nif [ -n \"$TRACE\" ]; then\n  set -x\nfi\n\nexport KIND_K8S_VERSION=\"${KIND_K8S_VERSION:-\"v1.35.0\"}\"\ntools_k8s_version=$(convert_to_tools_ver \"${KIND_K8S_VERSION#v*}\")\nkind_version=0.29.0\ngoarch=amd64\n\nif [[ \"$OSTYPE\" == \"linux-gnu\" ]]; then\n  goos=\"linux\"\nelif [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n  goos=\"darwin\"\n#elif [[ \"$OS\" == \"Windows_NT\" ]]; then\n#  goos=\"windows\"\nelse\n  echo \"OS '$OSTYPE' not supported. Aborting.\" >&2\n  exit 1\nfi\n\n# Turn colors in this script off by setting the NO_COLOR variable in your\n# environment to any value:\n#\n# $ NO_COLOR=1 test.sh\nNO_COLOR=${NO_COLOR:-\"\"}\nif [ -z \"$NO_COLOR\" ]; then\n  header=$'\\e[1;33m'\n  reset=$'\\e[0m'\nelse\n  header=''\n  reset=''\nfi\n\nfunction header_text {\n  echo \"$header$*$reset\"\n}\n\n# Certain tools are installed to GOBIN.\nexport PATH=\"$(go env GOPATH)/bin:${PATH}\"\n\n# Kubebuilder's bin path should be the last added to PATH such that it is preferred.\ntmp_root=/tmp\nkb_root_dir=$tmp_root/kubebuilder\nmkdir -p \"$kb_root_dir\"\nexport PATH=\"${kb_root_dir}/bin:${PATH}\"\n\n# Skip fetching and untaring the tools by setting the SKIP_FETCH_TOOLS variable\n# in your environment to any value:\n#\n# $ SKIP_FETCH_TOOLS=1 ./test.sh\n#\n# If you skip fetching tools, this script will use the tools already on your\n# machine, but rebuild the kubebuilder and kubebuilder-bin binaries.\nSKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-\"\"}\n\n# Build kubebuilder\nfunction build_kb {\n  header_text \"Building kubebuilder\"\n\n  go build -o \"${kb_root_dir}/bin/kubebuilder\"\n  kb=\"${kb_root_dir}/bin/kubebuilder\"\n}\n\n# Fetch k8s API tools and manage them globally with setup-envtest.\nfunction fetch_tools {\n  if ! is_installed setup-envtest; then\n    header_text \"Installing setup-envtest to $(go env GOPATH)/bin\"\n\n    # TODO: Current workaround for setup-envtest compatibility\n    # Due to past instances where controller-runtime maintainers released\n    # versions without corresponding branches, directly relying on branches\n    # poses a risk of breaking the Kubebuilder chain. Such practices may\n    # change over time, potentially leading to compatibility issues. This\n    # approach, although not ideal, remains the best solution for ensuring\n    # compatibility with controller-runtime releases as of now. For more\n    # details on the quest for a more robust solution, refer to the issue\n    # raised in the controller-runtime repository: https://github.com/kubernetes-sigs/controller-runtime/issues/2744\n    go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.22\n  fi\n\n  if [ -z \"$SKIP_FETCH_TOOLS\" ]; then\n    header_text \"Installing e2e tools with setup-envtest\"\n\n    setup-envtest use $tools_k8s_version\n  fi\n\n  # Export KUBEBUILDER_ASSETS.\n  eval $(setup-envtest use -i -p env $tools_k8s_version)\n  # Downloaded tools should be used instead of counterparts present in the environment.\n  export PATH=\"${KUBEBUILDER_ASSETS}:${PATH}\"\n}\n\n# Installing kind in a temporal dir if no previously installed to GOBIN.\nfunction install_kind {\n  if ! is_installed kind ; then\n    header_text \"Installing kind to $(go env GOPATH)/bin\"\n\n    go install sigs.k8s.io/kind@v$kind_version\n  fi\n}\n\n# Check if a program is previously installed\nfunction is_installed {\n  if command -v $1 &>/dev/null; then\n    return 0\n  fi\n  return 1\n}\n\nfunction listPkgDirs() {\n\tgo list -f '{{.Dir}}' ./... | grep -v generated\n}\n\n#Lists all go files\nfunction listFiles() {\n\t# pipeline is much faster than for loop\n\tlistPkgDirs | xargs -I {} find {} \\( -name '*.go' -o -name '*.sh' \\)  | grep -v generated\n}\n"
  },
  {
    "path": "test/e2e/all/e2e_suite_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage all\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Run unified e2e tests using the Ginkgo runner.\n// This consolidates all plugin tests (v4, helm, deployimage) into a single suite\n// to share infrastructure setup (cert-manager, Prometheus) and reduce overhead.\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting unified Kubebuilder e2e suite (all plugins)\\n\")\n\tRunSpecs(t, \"Kubebuilder Unified E2E Suite\")\n}\n\n// BeforeSuite runs once before all specs to set up shared infrastructure.\n// This is run ONCE instead of 3 times (once per plugin), significantly reducing test time.\nvar _ = BeforeSuite(func() {\n\tvar err error\n\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"\\n=== Setting up shared test infrastructure ===\\n\")\n\n\tkbc, err := utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(kbc.Prepare()).To(Succeed())\n\n\tBy(\"installing cert-manager bundle (shared across all tests)\")\n\tExpect(kbc.InstallCertManager()).To(Succeed())\n\n\tBy(\"installing Prometheus operator (shared across all tests)\")\n\tExpect(kbc.InstallPrometheusOperManager()).To(Succeed())\n\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"=== Shared infrastructure ready ===\\n\\n\")\n})\n\n// AfterSuite runs once after all specs to clean up shared infrastructure.\nvar _ = AfterSuite(func() {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"\\n=== Cleaning up shared test infrastructure ===\\n\")\n\n\tkbc, err := utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(kbc.Prepare()).To(Succeed())\n\n\tBy(\"uninstalling Prometheus operator\")\n\tkbc.UninstallPrometheusOperManager()\n\n\tBy(\"uninstalling cert-manager bundle\")\n\tkbc.UninstallCertManager()\n\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"=== Cleanup complete ===\\n\")\n})\n"
  },
  {
    "path": "test/e2e/all/plugin_deployimage_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage all\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Test specs for deploy-image plugin\nvar _ = Describe(\"kubebuilder\", func() {\n\tContext(\"deploy image plugin\", func() {\n\t\tvar kbc *utils.TestContext\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tkbc, err = utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kbc.Prepare()).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"clean up API objects created during the test\")\n\t\t\tExpect(kbc.Make(\"undeploy\")).To(Succeed())\n\n\t\t\tBy(\"removing controller image and working dir\")\n\t\t\tkbc.Destroy()\n\t\t})\n\n\t\tIt(\"should generate a runnable project with deploy-image/v1-alpha options \", func() {\n\t\t\tgenerateDeployImageWithOptions(kbc)\n\t\t\trunDeployImageTests(kbc)\n\t\t})\n\n\t\tIt(\"should generate a runnable project with deploy-image/v1-alpha without options \", func() {\n\t\t\tgenerateDeployImage(kbc)\n\t\t\trunDeployImageTests(kbc)\n\t\t})\n\t})\n})\n\n// generateDeployImageWithOptions implements a go/v4 plugin project and scaffold an API using the image options\nfunc generateDeployImageWithOptions(kbc *utils.TestContext) {\n\tinitDeployImageProject(kbc)\n\n\tBy(\"creating API definition with deploy-image/v1-alpha plugin with options\")\n\terr := kbc.CreateAPI(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--plugins\", \"deploy-image/v1-alpha\",\n\t\t\"--image\", \"memcached:1.6.26-alpine3.19\",\n\t\t\"--image-container-port\", \"11211\",\n\t\t\"--image-container-command\", \"memcached,--memory-limit=64,-o,modern,-v\",\n\t\t\"--run-as-user\", \"1001\",\n\t\t\"--make=false\",\n\t\t\"--manifests=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to create API definition with deploy-image/v1-alpha\")\n}\n\n// generateDeployImage implements a go/v4 plugin project and scaffold an API using the deploy image plugin\nfunc generateDeployImage(kbc *utils.TestContext) {\n\tinitDeployImageProject(kbc)\n\n\tBy(\"creating API definition with deploy-image/v1-alpha plugin\")\n\terr := kbc.CreateAPI(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--plugins\", \"deploy-image/v1-alpha\",\n\t\t\"--image\", \"busybox:1.36.1\",\n\t\t\"--run-as-user\", \"1001\",\n\t\t\"--make=false\",\n\t\t\"--manifests=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to create API definition\")\n}\n\nfunc initDeployImageProject(kbc *utils.TestContext) {\n\tBy(\"initializing a project\")\n\terr := kbc.Init(\n\t\t\"--plugins\", \"go/v4\",\n\t\t\"--project-version\", \"3\",\n\t\t\"--domain\", kbc.Domain,\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to initialize project\")\n}\n\n// runDeployImageTests runs a set of e2e tests for a scaffolded deploy-image project.\nfunc runDeployImageTests(kbc *utils.TestContext) {\n\tvar controllerPodName string\n\tvar err error\n\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\tSetDefaultEventuallyTimeout(time.Minute)\n\n\tBy(\"updating the go.mod\")\n\tExpect(kbc.Tidy()).To(Succeed())\n\n\tBy(\"run make manifests\")\n\tExpect(kbc.Make(\"manifests\")).To(Succeed())\n\n\tBy(\"run make generate\")\n\tExpect(kbc.Make(\"generate\")).To(Succeed())\n\n\tBy(\"run make all\")\n\tExpect(kbc.Make(\"all\")).To(Succeed())\n\n\tBy(\"run make install\")\n\tExpect(kbc.Make(\"install\")).To(Succeed())\n\n\tBy(\"building the controller image\")\n\tExpect(kbc.Make(\"docker-build\", \"IMG=\"+kbc.ImageName)).To(Succeed())\n\n\tBy(\"loading the controller docker image into the kind cluster\")\n\tExpect(kbc.LoadImageToKindCluster()).To(Succeed())\n\n\tBy(\"deploying the controller-manager\")\n\tcmd := exec.Command(\"make\", \"deploy\", \"IMG=\"+kbc.ImageName)\n\tout, err := kbc.Run(cmd)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(string(out)).NotTo(ContainSubstring(\"Warning: would violate PodSecurity\"))\n\n\tBy(\"validating that the controller-manager pod is running as expected\")\n\tverifyControllerUp := func(g Gomega) {\n\t\t// Get pod name\n\t\tvar podOutput string\n\t\tpodOutput, err = kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\"-o\", \"go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}\"+\n\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tpodNames := util.GetNonEmptyLines(podOutput)\n\t\tg.Expect(podNames).To(HaveLen(1), \"wrong number of controller-manager pods\")\n\t\tcontrollerPodName = podNames[0]\n\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t// Validate pod status\n\t\tg.Expect(kbc.Kubectl.Get(true, \"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\")).\n\t\t\tTo(Equal(\"Running\"), \"incorrect controller pod status\")\n\t}\n\tdefer func() {\n\t\tout, errDescribe := kbc.Kubectl.CommandInNamespace(\"describe\", \"all\")\n\t\tExpect(errDescribe).NotTo(HaveOccurred())\n\t\t_, _ = fmt.Fprintln(GinkgoWriter, out)\n\t}()\n\tEventually(verifyControllerUp).Should(Succeed())\n\n\tBy(\"creating an instance of the CR\")\n\tsampleFile := filepath.Join(\"config\", \"samples\",\n\t\tfmt.Sprintf(\"%s_%s_%s.yaml\", kbc.Group, kbc.Version, strings.ToLower(kbc.Kind)))\n\n\tsampleFilePath, err := filepath.Abs(filepath.Join(fmt.Sprintf(\"e2e-%s\", kbc.TestSuffix), sampleFile))\n\tExpect(err).To(Not(HaveOccurred()))\n\n\tEventually(func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Apply(true, \"-f\", sampleFilePath)).Error().NotTo(HaveOccurred())\n\t}).Should(Succeed())\n\n\tBy(\"validating that pod(s) status.phase=Running\")\n\tverifyMemcachedPodStatus := func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Get(true, \"pods\", \"-l\",\n\t\t\tfmt.Sprintf(\"app.kubernetes.io/name=e2e-%s\", kbc.TestSuffix),\n\t\t\t\"-o\", \"jsonpath={.items[*].status}\",\n\t\t)).To(ContainSubstring(\"\\\"phase\\\":\\\"Running\\\"\"))\n\t}\n\tEventually(verifyMemcachedPodStatus).Should(Succeed())\n\n\tBy(\"validating that the status of the custom resource created is updated or not\")\n\tverifyAvailableStatus := func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Get(true, strings.ToLower(kbc.Kind),\n\t\t\tstrings.ToLower(kbc.Kind)+\"-sample\",\n\t\t\t\"-o\", \"jsonpath={.status.conditions}\")).To(ContainSubstring(\"Available\"),\n\t\t\t`status condition with type \"Available\" should be set`)\n\t}\n\tEventually(verifyAvailableStatus).Should(Succeed())\n\n\tBy(\"validating the finalizer\")\n\tEventually(func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Delete(true, \"-f\", sampleFilePath)).Error().NotTo(HaveOccurred())\n\t}).Should(Succeed())\n\n\tEventually(func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Get(true, \"events\", \"--field-selector=type=Warning\",\n\t\t\t\"-o\", \"jsonpath={.items[*].message}\",\n\t\t)).To(ContainSubstring(\"is being deleted from the namespace\"))\n\t}).Should(Succeed())\n}\n"
  },
  {
    "path": "test/e2e/all/plugin_helm_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage all\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/internal/helpers\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Test specs for helm/v2-alpha plugin\nvar _ = Describe(\"kubebuilder\", func() {\n\tContext(\"plugin helm/v2-alpha\", func() {\n\t\tvar kbc *utils.TestContext\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tkbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, \"GO111MODULE=on\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kbc.Prepare()).To(Succeed())\n\n\t\t\tBy(\"installing Helm binary for chart operations\")\n\t\t\tExpect(kbc.InstallHelm()).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing restricted namespace label\")\n\t\t\t_ = kbc.RemoveNamespaceLabelToEnforceRestricted()\n\n\t\t\tBy(\"uninstalling Helm Release (if installed)\")\n\t\t\t_ = kbc.UninstallHelmRelease()\n\n\t\t\tBy(\"cleaning up CRDs that were preserved by crd.keep=true\")\n\t\t\tdomainSuffix := fmt.Sprintf(\".example.com%s\", kbc.TestSuffix)\n\t\t\tlistCmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"name\")\n\t\t\tif output, err := kbc.Run(listCmd); err == nil {\n\t\t\t\tfor crdName := range strings.SplitSeq(strings.TrimSpace(string(output)), \"\\n\") {\n\t\t\t\t\tif crdName != \"\" && strings.Contains(crdName, domainSuffix) {\n\t\t\t\t\t\tdeleteCmd := exec.Command(\"kubectl\", \"delete\", crdName, \"--ignore-not-found\")\n\t\t\t\t\t\t_, _ = kbc.Run(deleteCmd)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tBy(\"removing controller image and working dir\")\n\t\t\tkbc.Destroy()\n\t\t})\n\n\t\tIt(\"should generate a runnable project using webhooks and installed with the HelmChart\", func() {\n\t\t\thelpers.GenerateV4(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\n\t\t\tBy(\"upgrading the release to 3 replicas and verifying \" +\n\t\t\t\t\"manager.replicas is respected (leader election ensures only one active)\")\n\t\t\tExpect(kbc.HelmUpgradeReleaseWithReplicas(3)).To(Succeed())\n\t\t\tdeploymentName := fmt.Sprintf(\"e2e-%s-controller-manager\", kbc.TestSuffix)\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tout, err := kbc.Kubectl.Get(\n\t\t\t\t\ttrue,\n\t\t\t\t\t\"deployment\", deploymentName,\n\t\t\t\t\t\"-o\", \"jsonpath={.spec.replicas}\",\n\t\t\t\t)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\treplicas, atoiErr := strconv.Atoi(strings.TrimSpace(out))\n\t\t\t\tg.Expect(atoiErr).NotTo(HaveOccurred(), \"replicas field is not an integer\")\n\t\t\t\tg.Expect(replicas).To(Equal(3), \"expected deployment spec.replicas to be 3 after helm upgrade\")\n\t\t\t}, helpers.DefaultTimeout, 30*time.Second).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should generate a runnable project without metrics exposed\", func() {\n\t\t\thelpers.GenerateV4WithoutMetrics(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         false,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with metrics protected by network policies\", func() {\n\t\t\thelpers.GenerateV4WithNetworkPoliciesWithoutWebhooks(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         false,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with webhooks and metrics protected by network policies\", func() {\n\t\t\thelpers.GenerateV4WithNetworkPolicies(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with the manager running as restricted and without webhooks\", func() {\n\t\t\thelpers.GenerateV4WithoutWebhooks(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         false,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should work with Helm chart customizations (fullnameOverride and cert-manager)\", func() {\n\t\t\tBy(\"generating a full-featured project with webhooks, metrics, and conversion webhooks\")\n\t\t\thelpers.GenerateV4(kbc)\n\n\t\t\tBy(\"building installer and generating helm chart\")\n\t\t\tExpect(kbc.Make(\"build-installer\")).To(Succeed())\n\t\t\terr := kbc.EditHelmPlugin()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"customizing chart name via fullnameOverride to validate runtime behavior\")\n\t\t\tvaluesPath := filepath.Join(kbc.Dir, \"dist\", \"chart\", \"values.yaml\")\n\t\t\terr = pluginutil.ReplaceInFile(valuesPath,\n\t\t\t\t`# fullnameOverride: \"\"`,\n\t\t\t\t`fullnameOverride: \"custom-operator\"`)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"deploying with custom chart name - validates cert-manager and all resources work correctly\")\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:           true,\n\t\t\t\tHasMetrics:           true,\n\t\t\t\tHasNetworkPolicies:   false,\n\t\t\t\tInstallMethod:        helpers.InstallMethodHelm,\n\t\t\t\tHelmFullnameOverride: \"custom-operator\",\n\t\t\t\tSkipChartGeneration:  true, // Chart already generated and customized above\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a namespeced runnable project using webhooks and installed with the HelmChart\", func() {\n\t\t\thelpers.GenerateV4Namespaced(kbc)\n\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tIsNamespaced:       true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodHelm,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should delete CRDs on helm uninstall when crd.keep=false\", func() {\n\t\t\tBy(\"generating a project with webhooks\")\n\t\t\thelpers.GenerateV4(kbc)\n\n\t\t\tBy(\"building installer and generating helm chart\")\n\t\t\tExpect(kbc.Make(\"build-installer\")).To(Succeed())\n\t\t\terr := kbc.EditHelmPlugin()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"installing helm chart with crd.keep=false\")\n\t\t\tExpect(kbc.HelmInstallReleaseWithOptions(false)).To(Succeed())\n\n\t\t\tBy(\"verifying CRDs exist after install\")\n\t\t\tdomainSuffix := fmt.Sprintf(\".example.com%s\", kbc.TestSuffix)\n\t\t\tverifyCRDsExist := func(g Gomega) {\n\t\t\t\tlistCmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"name\")\n\t\t\t\toutput, err := kbc.Run(listCmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"failed to list CRDs\")\n\t\t\t\tg.Expect(string(output)).To(ContainSubstring(domainSuffix),\n\t\t\t\t\t\"expected CRDs matching domain suffix %s to exist\", domainSuffix)\n\t\t\t}\n\t\t\tverifyCRDsExist(Default)\n\n\t\t\tBy(\"uninstalling helm release\")\n\t\t\tExpect(kbc.UninstallHelmRelease()).To(Succeed())\n\n\t\t\tBy(\"verifying CRDs are deleted after uninstall (crd.keep=false)\")\n\t\t\tverifyCRDsDeleted := func(g Gomega) {\n\t\t\t\tlistCmd := exec.Command(\"kubectl\", \"get\", \"crds\", \"-o\", \"name\")\n\t\t\t\toutput, err := kbc.Run(listCmd)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// If we can't list CRDs, assume they're gone\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor crdName := range strings.SplitSeq(strings.TrimSpace(string(output)), \"\\n\") {\n\t\t\t\t\tg.Expect(crdName).NotTo(ContainSubstring(domainSuffix),\n\t\t\t\t\t\t\"CRD %s still exists but should have been deleted\", crdName)\n\t\t\t\t}\n\t\t\t}\n\t\t\tEventually(verifyCRDsDeleted, helpers.DefaultTimeout, 30*time.Second).Should(Succeed())\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "test/e2e/all/plugin_v4_test.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage all\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/internal/helpers\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// Test specs for go/v4 plugin\nvar _ = Describe(\"kubebuilder\", func() {\n\tContext(\"plugin go/v4\", func() {\n\t\tvar kbc *utils.TestContext\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tkbc, err = utils.NewTestContext(util.KubebuilderBinName, \"GO111MODULE=on\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kbc.Prepare()).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing restricted namespace label\")\n\t\t\t_ = kbc.RemoveNamespaceLabelToEnforceRestricted()\n\n\t\t\tBy(\"undeploy the project\")\n\t\t\t_ = kbc.Make(\"undeploy\")\n\n\t\t\tBy(\"uninstalling the project\")\n\t\t\t_ = kbc.Make(\"uninstall\")\n\n\t\t\tBy(\"removing controller image and working dir\")\n\t\t\tkbc.Destroy()\n\t\t})\n\n\t\tIt(\"should generate a runnable project\", func() {\n\t\t\thelpers.GenerateV4(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with the Installer\", func() {\n\t\t\thelpers.GenerateV4(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodInstaller,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project without metrics exposed\", func() {\n\t\t\thelpers.GenerateV4WithoutMetrics(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         false,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with metrics protected by network policies\", func() {\n\t\t\thelpers.GenerateV4WithNetworkPoliciesWithoutWebhooks(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         false,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with webhooks and metrics protected by network policies\", func() {\n\t\t\thelpers.GenerateV4WithNetworkPolicies(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with the manager running \"+\n\t\t\t\"as restricted and without webhooks\", func() {\n\t\t\thelpers.GenerateV4WithoutWebhooks(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         false,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project with custom webhook paths\", func() {\n\t\t\thelpers.GenerateV4WithCustomWebhookPath(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should generate a runnable project\", func() {\n\t\t\thelpers.GenerateV4Namespaced(kbc)\n\t\t\thelpers.Run(kbc, helpers.RunOptions{\n\t\t\t\tHasWebhook:         true,\n\t\t\t\tHasMetrics:         true,\n\t\t\t\tHasNetworkPolicies: false,\n\t\t\t\tIsNamespaced:       true,\n\t\t\t\tInstallMethod:      helpers.InstallMethodKustomize,\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "test/e2e/ci.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\nsource \"$(dirname \"$0\")/setup.sh\"\n\nexport KIND_CLUSTER=\"kind\"\ncreate_cluster ${KIND_K8S_VERSION}\ntrap delete_cluster EXIT\n\ntest_cluster -v -ginkgo.vv\n"
  },
  {
    "path": "test/e2e/internal/helpers/generate_v4.go",
    "content": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" //nolint:staticcheck\n\t. \"github.com/onsi/gomega\"    //nolint:staticcheck\n\n\tpluginutil \"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\n// GenerateV4 implements a go/v4 plugin project defined by a TestContext.\nfunc GenerateV4(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tBy(\"scaffolding mutating and validating webhooks\")\n\terr := kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to scaffolding mutating webhook\")\n\n\tBy(\"implementing the mutating and validating webhooks\")\n\twebhookFilePath := filepath.Join(\n\t\tkbc.Dir, \"internal/webhook\", kbc.Version,\n\t\tfmt.Sprintf(\"%s_webhook.go\", strings.ToLower(kbc.Kind)))\n\terr = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to implement webhooks\")\n\n\tscaffoldConversionWebhook(kbc)\n\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"prometheus\", \"kustomization.yaml\"),\n\t\tmonitorTLSPatch, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertPatch, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertReplaces, \"#\")).To(Succeed())\n}\n\n// GenerateV4WithoutMetrics implements a go/v4 plugin project defined by a TestContext.\nfunc GenerateV4WithoutMetrics(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tBy(\"scaffolding mutating and validating webhooks\")\n\terr := kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to scaffolding mutating webhook\")\n\n\tBy(\"implementing the mutating and validating webhooks\")\n\twebhookFilePath := filepath.Join(\n\t\tkbc.Dir, \"internal/webhook\", kbc.Version,\n\t\tfmt.Sprintf(\"%s_webhook.go\", strings.ToLower(kbc.Kind)))\n\terr = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to implement webhooks\")\n\n\tscaffoldConversionWebhook(kbc)\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\t// Disable metrics\n\tExpectWithOffset(1, pluginutil.CommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"- metrics_service.yaml\", \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.CommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsTarget, \"#\")).To(Succeed())\n}\n\n// GenerateV4WithoutMetrics implements a go/v4 plugin project defined by a TestContext.\nfunc GenerateV4WithNetworkPoliciesWithoutWebhooks(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsTarget, \"#\")).To(Succeed())\n\tBy(\"uncomment kustomization.yaml to enable network policy\")\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../network-policy\", \"#\")).To(Succeed())\n}\n\n// GenerateV4WithNetworkPolicies implements a go/v4 plugin project defined by a TestContext.\nfunc GenerateV4WithNetworkPolicies(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tBy(\"scaffolding mutating and validating webhooks\")\n\terr := kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to scaffolding mutating webhook\")\n\n\tBy(\"implementing the mutating and validating webhooks\")\n\twebhookFilePath := filepath.Join(\n\t\tkbc.Dir, \"internal/webhook\", kbc.Version,\n\t\tfmt.Sprintf(\"%s_webhook.go\", strings.ToLower(kbc.Kind)))\n\terr = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to implement webhooks\")\n\n\tscaffoldConversionWebhook(kbc)\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsTarget, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertPatch, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertReplaces, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"prometheus\", \"kustomization.yaml\"),\n\t\tmonitorTLSPatch, \"#\")).To(Succeed())\n\n\tBy(\"uncomment kustomization.yaml to enable network policy\")\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../network-policy\", \"#\")).To(Succeed())\n}\n\n// GenerateV4WithoutWebhooks implements a go/v4 plugin with APIs and enable Prometheus and CertManager\nfunc GenerateV4WithoutWebhooks(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n}\n\n// GenerateV4WithCustomWebhookPath tests webhooks with custom paths\nfunc GenerateV4WithCustomWebhookPath(kbc *utils.TestContext) {\n\tinitingTheProject(kbc)\n\tcreatingAPI(kbc)\n\n\tBy(\"scaffolding both defaulting and validation webhooks with different custom paths\")\n\terr := kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t\t\"--defaulting-path=/custom-mutate-path\",\n\t\t\"--validation-path=/custom-validate-path\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to scaffold webhooks with custom paths\")\n\n\tBy(\"verifying custom webhook paths in generated webhook file\")\n\twebhookFilePath := filepath.Join(\n\t\tkbc.Dir, \"internal/webhook\", kbc.Version,\n\t\tfmt.Sprintf(\"%s_webhook.go\", strings.ToLower(kbc.Kind)))\n\n\t// Read the webhook file and check if both custom paths are present\n\tcontent, err := os.ReadFile(webhookFilePath)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to read webhook file\")\n\tExpect(string(content)).To(ContainSubstring(\"path=/custom-mutate-path\"),\n\t\t\"Webhook file should contain custom defaulting path\")\n\tExpect(string(content)).To(ContainSubstring(\"path=/custom-validate-path\"),\n\t\t\"Webhook file should contain custom validation path\")\n\n\tBy(\"implementing the webhooks\")\n\terr = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to implement webhook\")\n\n\tscaffoldConversionWebhook(kbc)\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\n\tBy(\"verifying that --defaulting-path requires --defaulting flag\")\n\terr = kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", \"InvalidTest\",\n\t\t\"--defaulting-path=/invalid-path\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).To(HaveOccurred(), \"Should fail when --defaulting-path is used without --defaulting\")\n\tExpect(err.Error()).To(ContainSubstring(\"--defaulting-path can only be used with --defaulting\"))\n\n\tBy(\"verifying that --validation-path requires --programmatic-validation flag\")\n\terr = kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", \"InvalidTest\",\n\t\t\"--validation-path=/invalid-path\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).To(HaveOccurred(), \"Should fail when --validation-path is used without --programmatic-validation\")\n\tExpect(err.Error()).To(ContainSubstring(\"--validation-path can only be used with --programmatic-validation\"))\n}\n\nfunc creatingAPI(kbc *utils.TestContext) {\n\tBy(\"creating API definition\")\n\terr := kbc.CreateAPI(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--namespaced\",\n\t\t\"--resource\",\n\t\t\"--controller\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to create API\")\n\n\tBy(\"implementing the API\")\n\tExpectWithOffset(1, pluginutil.InsertCode(\n\t\tfilepath.Join(kbc.Dir, \"api\", kbc.Version, fmt.Sprintf(\"%s_types.go\", strings.ToLower(kbc.Kind))),\n\t\tfmt.Sprintf(`type %sSpec struct {\n`, kbc.Kind),\n\t\t`\t// +optional\nCount int `+\"`\"+`json:\"count,omitempty\"`+\"`\"+`\n`)).Should(Succeed())\n}\n\nfunc initingTheProject(kbc *utils.TestContext) {\n\tBy(\"initializing a project\")\n\terr := kbc.Init(\n\t\t\"--plugins\", \"go/v4\",\n\t\t\"--project-version\", \"3\",\n\t\t\"--domain\", kbc.Domain,\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to initialize project\")\n}\n\nfunc initingNamespacedProject(kbc *utils.TestContext) {\n\tBy(\"initializing a namespace-scoped project\")\n\terr := kbc.Init(\n\t\t\"--plugins\", \"go/v4\",\n\t\t\"--project-version\", \"3\",\n\t\t\"--domain\", kbc.Domain,\n\t\t\"--namespaced\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to initialize namespace-scoped project\")\n}\n\nconst metricsTarget = `- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment`\n\n// scaffoldConversionWebhook sets up conversion webhooks for testing the ConversionTest API\nfunc scaffoldConversionWebhook(kbc *utils.TestContext) {\n\tBy(\"scaffolding conversion webhooks for testing ConversionTest v1 to v2 conversion\")\n\n\t// Create API for v1 (hub) with conversion enabled\n\terr := kbc.CreateAPI(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", \"v1\",\n\t\t\"--kind\", \"ConversionTest\",\n\t\t\"--controller=true\",\n\t\t\"--resource=true\",\n\t\t\"--make=false\",\n\t)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"failed to create v1 API for conversion testing\")\n\n\t// Create API for v2 (spoke) without a controller\n\terr = kbc.CreateAPI(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", \"v2\",\n\t\t\"--kind\", \"ConversionTest\",\n\t\t\"--controller=false\",\n\t\t\"--resource=true\",\n\t\t\"--make=false\",\n\t)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"failed to create v2 API for conversion testing\")\n\n\t// Create the conversion webhook for v1\n\tBy(\"setting up the conversion webhook for v1\")\n\terr = kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", \"v1\",\n\t\t\"--kind\", \"ConversionTest\",\n\t\t\"--conversion\",\n\t\t\"--spoke\", \"v2\",\n\t\t\"--make=false\",\n\t)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"failed to create conversion webhook for v1\")\n\n\t// Insert Size field in v1\n\tBy(\"implementing the size spec in v1\")\n\tExpectWithOffset(1, pluginutil.InsertCode(\n\t\tfilepath.Join(kbc.Dir, \"api\", \"v1\", \"conversiontest_types.go\"),\n\t\t\"Foo *string `json:\\\"foo,omitempty\\\"`\",\n\t\t\"\\n\\tSize int `json:\\\"size,omitempty\\\"` // Number of desired instances\",\n\t)).NotTo(HaveOccurred(), \"failed to add size spec to conversiontest_types v1\")\n\n\t// Insert Replicas field in v2\n\tBy(\"implementing the replicas spec in v2\")\n\tExpectWithOffset(1, pluginutil.InsertCode(\n\t\tfilepath.Join(kbc.Dir, \"api\", \"v2\", \"conversiontest_types.go\"),\n\t\t\"Foo *string `json:\\\"foo,omitempty\\\"`\",\n\t\t\"\\n\\tReplicas int `json:\\\"replicas,omitempty\\\"` // Number of replicas\",\n\t)).NotTo(HaveOccurred(), \"failed to add replicas spec to conversiontest_conversion.go v2\")\n\n\terr = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, \"api/v2/conversiontest_conversion.go\"),\n\t\t\"// dst.Spec.Size = src.Spec.Replicas\",\n\t\t\"dst.Spec.Size = src.Spec.Replicas\")\n\tExpect(err).NotTo(HaveOccurred(), \"failed to implement conversion logic from v1 to v2\")\n\n\terr = pluginutil.ReplaceInFile(filepath.Join(kbc.Dir, \"api/v2/conversiontest_conversion.go\"),\n\t\t\"// dst.Spec.Replicas = src.Spec.Size\",\n\t\t\"dst.Spec.Replicas = src.Spec.Size\")\n\tExpect(err).NotTo(HaveOccurred(), \"failed to implement conversion logic from v2 to v1\")\n}\n\nconst monitorTLSPatch = `#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor`\n\nconst metricsCertPatch = `#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment`\n\nconst metricsCertReplaces = `# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true`\n\n// GenerateV4Namespaced implements a go/v4 plugin namespace-scoped project defined by a TestContext.\nfunc GenerateV4Namespaced(kbc *utils.TestContext) {\n\tinitingNamespacedProject(kbc)\n\tcreatingAPI(kbc)\n\n\tBy(\"scaffolding mutating and validating webhooks\")\n\terr := kbc.CreateWebhook(\n\t\t\"--group\", kbc.Group,\n\t\t\"--version\", kbc.Version,\n\t\t\"--kind\", kbc.Kind,\n\t\t\"--defaulting\",\n\t\t\"--programmatic-validation\",\n\t\t\"--make=false\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to scaffolding mutating webhook\")\n\n\tBy(\"implementing the mutating and validating webhooks\")\n\twebhookFilePath := filepath.Join(\n\t\tkbc.Dir, \"internal/webhook\", kbc.Version,\n\t\tfmt.Sprintf(\"%s_webhook.go\", strings.ToLower(kbc.Kind)))\n\terr = utils.ImplementWebhooks(webhookFilePath, strings.ToLower(kbc.Kind))\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to implement webhooks\")\n\n\tscaffoldConversionWebhook(kbc)\n\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\t\"#- ../prometheus\", \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"prometheus\", \"kustomization.yaml\"),\n\t\tmonitorTLSPatch, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertPatch, \"#\")).To(Succeed())\n\tExpectWithOffset(1, pluginutil.UncommentCode(\n\t\tfilepath.Join(kbc.Dir, \"config\", \"default\", \"kustomization.yaml\"),\n\t\tmetricsCertReplaces, \"#\")).To(Succeed())\n}\n"
  },
  {
    "path": "test/e2e/internal/helpers/plugin_test_helper.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\" //nolint:staticcheck\n\t. \"github.com/onsi/gomega\"    //nolint:staticcheck\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nconst (\n\t// DefaultTimeout is the default timeout for Eventually checks\n\tDefaultTimeout = 3 * time.Minute\n\t// DefaultPollingInterval is the default polling interval for Eventually checks\n\tDefaultPollingInterval = time.Second\n\n\t// defaultTimeout is the default timeout for Eventually checks (package-internal alias)\n\tdefaultTimeout = DefaultTimeout\n\t// defaultPollingInterval is the default polling interval for Eventually checks (package-internal alias)\n\tdefaultPollingInterval = DefaultPollingInterval\n)\n\n// InstallMethod defines how the project will be deployed\ntype InstallMethod string\n\nconst (\n\t// InstallMethodKustomize uses `make deploy` (default)\n\tInstallMethodKustomize InstallMethod = \"kustomize\"\n\t// InstallMethodInstaller uses `build-installer` and applies dist/install.yaml\n\tInstallMethodInstaller InstallMethod = \"installer\"\n\t// InstallMethodHelm uses Helm chart installation\n\tInstallMethodHelm InstallMethod = \"helm\"\n)\n\n// RunOptions configures the Run test execution\ntype RunOptions struct {\n\t// HasWebhook indicates if webhooks are enabled\n\tHasWebhook bool\n\t// HasMetrics indicates if metrics are enabled\n\tHasMetrics bool\n\t// HasNetworkPolicies indicates if network policies are enabled\n\tHasNetworkPolicies bool\n\t// IsNamespaced indicates if project is namespace-scoped\n\tIsNamespaced bool\n\t// InstallMethod specifies how to install the project\n\tInstallMethod InstallMethod\n\t// HelmFullnameOverride sets fullnameOverride for Helm installations (only for InstallMethodHelm)\n\tHelmFullnameOverride string\n\t// SkipChartGeneration skips build-installer and chart generation (chart already prepared externally)\n\tSkipChartGeneration bool\n}\n\n// Run executes common e2e tests for a scaffolded project.\n// This function is shared between go/v4 and helm/v2-alpha plugin tests.\nfunc Run(kbc *utils.TestContext, opts RunOptions) {\n\tvar controllerPodName string\n\tvar err error\n\n\t// Determine the name prefix for resources\n\t// If fullnameOverride is set, use that; otherwise use e2e-{suffix}\n\tnamePrefix := fmt.Sprintf(\"e2e-%s\", kbc.TestSuffix)\n\tif opts.HelmFullnameOverride != \"\" {\n\t\tnamePrefix = opts.HelmFullnameOverride\n\t}\n\n\t// For Helm installations with fullnameOverride, update ServiceAccount name\n\tif opts.InstallMethod == InstallMethodHelm {\n\t\tkbc.Kubectl.ServiceAccount = namePrefix + \"-controller-manager\"\n\t}\n\n\tBy(\"creating manager namespace\")\n\terr = kbc.CreateManagerNamespace()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\terr = kbc.LabelNamespacesToEnforceRestricted()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"updating the go.mod\")\n\terr = kbc.Tidy()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"run make all\")\n\terr = kbc.Make(\"all\")\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"building the controller image\")\n\terr = kbc.Make(\"docker-build\", \"IMG=\"+kbc.ImageName)\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"loading the controller docker image into the kind cluster\")\n\terr = kbc.LoadImageToKindCluster()\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// Deploy based on installation method\n\tswitch opts.InstallMethod {\n\tcase InstallMethodKustomize:\n\t\tBy(\"deploying the controller-manager via make deploy\")\n\t\tcmd := exec.Command(\"make\", \"deploy\", \"IMG=\"+kbc.ImageName)\n\t\t_, err = kbc.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\tcase InstallMethodInstaller:\n\t\tBy(\"building the installer\")\n\t\terr = kbc.Make(\"build-installer\", \"IMG=\"+kbc.ImageName)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tBy(\"deploying the controller-manager with the installer\")\n\t\t_, err = kbc.Kubectl.Apply(true, \"-f\", \"dist/install.yaml\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\tcase InstallMethodHelm:\n\t\tif !opts.SkipChartGeneration {\n\t\t\tBy(\"building the installer manifest for helm chart generation\")\n\t\t\terr = kbc.Make(\"build-installer\", \"IMG=\"+kbc.ImageName)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to build installer manifest\")\n\n\t\t\tBy(\"building the helm-chart\")\n\t\t\terr = kbc.EditHelmPlugin()\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to edit helm plugin\")\n\t\t}\n\n\t\tBy(\"deploying the controller-manager via Helm\")\n\t\terr = kbc.HelmInstallRelease()\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install Helm release\")\n\t}\n\n\tBy(\"Checking controllerManager and getting the name of the Pod\")\n\tcontrollerPodName = GetControllerPodName(kbc)\n\n\tBy(\"Checking if all flags are applied to the manager pod\")\n\tpodOutput, err := kbc.Kubectl.Get(\n\t\ttrue,\n\t\t\"pod\", controllerPodName,\n\t\t\"-o\", \"jsonpath={.spec.containers[0].args}\",\n\t)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(podOutput).To(ContainSubstring(\"leader-elect\"),\n\t\t\"Expected manager pod to have --leader-elect flag\")\n\tExpect(podOutput).To(ContainSubstring(\"health-probe-bind-address\"),\n\t\t\"Expected manager pod to have --health-probe-bind-address flag\")\n\n\tBy(\"validating that the Prometheus manager has provisioned the Service\")\n\tEventually(func(g Gomega) {\n\t\t_, err = kbc.Kubectl.Get(\n\t\t\tfalse,\n\t\t\t\"Service\", \"prometheus-operator\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t}, time.Minute, time.Second).Should(Succeed())\n\n\tBy(\"validating that the ServiceMonitor for Prometheus is applied in the namespace\")\n\t_, err = kbc.Kubectl.Get(\n\t\ttrue,\n\t\t\"ServiceMonitor\")\n\tExpect(err).NotTo(HaveOccurred())\n\n\tif opts.HasNetworkPolicies {\n\t\tif opts.HasMetrics {\n\t\t\tBy(\"labeling the namespace to allow consume the metrics\")\n\t\t\tExpect(kbc.Kubectl.Command(\"label\", \"namespaces\", kbc.Kubectl.Namespace,\n\t\t\t\t\"metrics=enabled\")).Error().NotTo(HaveOccurred())\n\n\t\t\tBy(\"Ensuring the Allow Metrics Traffic NetworkPolicy exists\", func() {\n\t\t\t\tvar output string\n\t\t\t\toutput, err = kbc.Kubectl.Get(\n\t\t\t\t\ttrue,\n\t\t\t\t\t\"networkpolicy\", fmt.Sprintf(\"e2e-%s-allow-metrics-traffic\", kbc.TestSuffix),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred(), \"NetworkPolicy allow-metrics-traffic should exist in the namespace\")\n\t\t\t\tExpect(output).To(ContainSubstring(\"allow-metrics-traffic\"), \"NetworkPolicy allow-metrics-traffic \"+\n\t\t\t\t\t\"should be present in the output\")\n\t\t\t})\n\t\t}\n\n\t\tif opts.HasWebhook {\n\t\t\tBy(\"labeling the namespace to allow webhooks traffic\")\n\t\t\t_, err = kbc.Kubectl.Command(\"label\", \"namespaces\", kbc.Kubectl.Namespace,\n\t\t\t\t\"webhook=enabled\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Ensuring the allow-webhook-traffic NetworkPolicy exists\", func() {\n\t\t\t\tvar output string\n\t\t\t\toutput, err = kbc.Kubectl.Get(\n\t\t\t\t\ttrue,\n\t\t\t\t\t\"networkpolicy\", fmt.Sprintf(\"e2e-%s-allow-webhook-traffic\", kbc.TestSuffix),\n\t\t\t\t)\n\t\t\t\tExpect(err).NotTo(HaveOccurred(), \"NetworkPolicy allow-webhook-traffic should exist in the namespace\")\n\t\t\t\tExpect(output).To(ContainSubstring(\"allow-webhook-traffic\"), \"NetworkPolicy allow-webhook-traffic \"+\n\t\t\t\t\t\"should be present in the output\")\n\t\t\t})\n\t\t}\n\t}\n\n\tif opts.HasWebhook {\n\t\tBy(\"validating that cert-manager has provisioned the certificate Secret\")\n\n\t\tverifyWebhookCert := func(g Gomega) {\n\t\t\tvar output string\n\t\t\toutput, err = kbc.Kubectl.Get(\n\t\t\t\ttrue,\n\t\t\t\t\"secrets\", \"webhook-server-cert\")\n\t\t\tg.Expect(err).ToNot(HaveOccurred(), \"webhook-server-cert should exist in the namespace\")\n\t\t\tg.Expect(output).To(ContainSubstring(\"webhook-server-cert\"))\n\t\t}\n\n\t\tEventually(verifyWebhookCert, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\t\tBy(\"validating that the mutating|validating webhooks have the CA injected\")\n\t\tverifyCAInjection := func(g Gomega) {\n\t\t\tvar mwhOutput, vwhOutput string\n\t\t\tmwhOutput, err = kbc.Kubectl.Get(\n\t\t\t\tfalse,\n\t\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\tfmt.Sprintf(\"%s-mutating-webhook-configuration\", namePrefix),\n\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t// check that ca should be long enough, because there may be a place holder \"\\n\"\n\t\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\n\t\t\tvwhOutput, err = kbc.Kubectl.Get(\n\t\t\t\tfalse,\n\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\tfmt.Sprintf(\"%s-validating-webhook-configuration\", namePrefix),\n\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t// check that ca should be long enough, because there may be a place holder \"\\n\"\n\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t}\n\n\t\tEventually(verifyCAInjection, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\t\tBy(\"validating that the CA injection is applied for CRD conversion\")\n\t\tcrdKind := \"ConversionTest\"\n\t\tverifyCAInjection = func(g Gomega) {\n\t\t\tvar crdOutput string\n\t\t\tcrdOutput, err = kbc.Kubectl.Get(\n\t\t\t\tfalse,\n\t\t\t\t\"customresourcedefinition.apiextensions.k8s.io\",\n\t\t\t\t\"-o\", fmt.Sprintf(\n\t\t\t\t\t\"jsonpath={.items[?(@.spec.names.kind=='%s')].spec.conversion.webhook.clientConfig.caBundle}\",\n\t\t\t\t\tcrdKind),\n\t\t\t)\n\t\t\tg.Expect(err).NotTo(HaveOccurred(),\n\t\t\t\t\"failed to get CRD conversion webhook configuration\")\n\n\t\t\t// Check if the CA bundle is populated (length > 10 to avoid placeholder values)\n\t\t\tg.Expect(len(crdOutput)).To(BeNumerically(\">\", 10),\n\t\t\t\t\"CA bundle should be injected into the CRD\")\n\t\t}\n\t\tEventually(verifyCAInjection, defaultTimeout, defaultPollingInterval).Should(Succeed(),\n\t\t\t\"CA injection validation failed\")\n\n\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\tvar output string\n\t\t\toutput, err = kbc.Kubectl.Get(\n\t\t\t\ttrue,\n\t\t\t\t\"endpointslices.discovery.k8s.io\",\n\t\t\t\t\"-l\", fmt.Sprintf(\"kubernetes.io/service-name=%s-webhook-service\", namePrefix),\n\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t}\n\t\tEventually(verifyWebhookEndpointsReady, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\ttime.Sleep(5 * time.Second)\n\t}\n\n\tBy(\"creating an instance of the CR\")\n\t// currently controller-runtime doesn't provide a readiness probe, we retry a few times\n\t// we can change it to probe the readiness endpoint after CR supports it.\n\tsampleFile := filepath.Join(\"config\", \"samples\",\n\t\tfmt.Sprintf(\"%s_%s_%s.yaml\", kbc.Group, kbc.Version, strings.ToLower(kbc.Kind)))\n\tsampleFilePath, err := filepath.Abs(filepath.Join(fmt.Sprintf(\"e2e-%s\", kbc.TestSuffix), sampleFile))\n\tExpect(err).To(Not(HaveOccurred()))\n\n\t// Add a field to the sample CR for testing\n\terr = util.ReplaceInFile(sampleFilePath, \"# TODO(user): Add fields here\", \"foo: bar\")\n\tExpect(err).To(Not(HaveOccurred()))\n\n\tapplySample := func(g Gomega) {\n\t\tg.Expect(kbc.Kubectl.Apply(true, \"-f\", sampleFile)).\n\t\t\tError().NotTo(HaveOccurred())\n\t}\n\tEventually(applySample, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\tif opts.HasMetrics {\n\t\tBy(\"checking the metrics values to validate that the created resource object gets reconciled\")\n\t\tmetricsOutput := GetMetricsOutput(controllerPodName, namePrefix, kbc)\n\t\tExpect(metricsOutput).To(ContainSubstring(fmt.Sprintf(\n\t\t\t`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t\tstrings.ToLower(kbc.Kind),\n\t\t)))\n\t}\n\n\tif !opts.HasMetrics {\n\t\tBy(\"validating the metrics endpoint is not working as expected\")\n\t\tValidateMetricsUnavailable(namePrefix, kbc)\n\t}\n\n\tif opts.HasWebhook {\n\t\tBy(\"validating that mutating and validating webhooks are working fine\")\n\t\tvar cnt string\n\t\tcnt, err = kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"-f\", sampleFile,\n\t\t\t\"-o\", \"go-template={{ .spec.count }}\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tcount, err2 := strconv.Atoi(cnt)\n\t\tExpect(err2).NotTo(HaveOccurred())\n\t\tExpect(count).To(BeNumerically(\"==\", 5))\n\t}\n\n\tif opts.HasWebhook {\n\t\tBy(\"creating a namespace\")\n\t\tnamespace := \"test-webhooks\"\n\t\t_, err = kbc.Kubectl.Command(\"create\", \"namespace\", namespace)\n\t\tExpect(err).To(Not(HaveOccurred()), \"namespace should be created successfully\")\n\n\t\tBy(\"applying the CR in the created namespace\")\n\n\t\tapplySampleNamespaced := func(g Gomega) {\n\t\t\t_, err = kbc.Kubectl.Apply(false, \"-n\", namespace, \"-f\", sampleFile)\n\t\t\tg.Expect(err).To(Not(HaveOccurred()))\n\t\t}\n\t\tEventually(applySampleNamespaced, 2*time.Minute, time.Second).Should(Succeed())\n\n\t\t// Note: Webhooks are cluster-scoped and validate/mutate CRs in ALL namespaces,\n\t\t// even in namespace-scoped managers. The manager won't reconcile CRs outside\n\t\t// its WATCH_NAMESPACE, but webhooks will still enforce validation/mutation rules.\n\t\tBy(\"validating that mutating webhooks are working fine outside of the manager's namespace\")\n\t\tvar cnt string\n\t\tcnt, err = kbc.Kubectl.Get(\n\t\t\tfalse,\n\t\t\t\"-n\", namespace,\n\t\t\t\"-f\", sampleFile,\n\t\t\t\"-o\", \"go-template={{ .spec.count }}\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tcount, err2 := strconv.Atoi(cnt)\n\t\tExpect(err2).NotTo(HaveOccurred())\n\t\tExpect(count).To(BeNumerically(\"==\", 5),\n\t\t\t\"the mutating webhook should set the count to 5\")\n\n\t\tBy(\"removing the namespace\")\n\t\tExpect(kbc.Kubectl.Command(\"delete\", \"namespace\", namespace)).\n\t\t\tError().NotTo(HaveOccurred(), \"namespace should be removed successfully\")\n\n\t\tBy(\"validating the conversion\")\n\n\t\t// Update the ConversionTest CR sample in v1 to set a specific `size`\n\t\tBy(\"modifying the ConversionTest CR sample to set `size` for conversion testing\")\n\t\tconversionCRFile := filepath.Join(\"config\", \"samples\",\n\t\t\tfmt.Sprintf(\"%s_v1_conversiontest.yaml\", kbc.Group))\n\t\tconversionCRPath := filepath.Join(kbc.Dir, conversionCRFile)\n\n\t\t// Edit the file to include `size` in the spec field for v1\n\t\terr = util.ReplaceInFile(conversionCRPath, \"# TODO(user): Add fields here\", `size: 3`)\n\t\tExpect(err).NotTo(HaveOccurred(), \"failed to replace spec in ConversionTest CR sample\")\n\n\t\t// Apply the ConversionTest Custom Resource in v1\n\t\tBy(\"applying the modified ConversionTest CR in v1 for conversion\")\n\t\t_, err = kbc.Kubectl.Apply(true, \"-f\", conversionCRPath)\n\t\tExpect(err).NotTo(HaveOccurred(), \"failed to apply modified ConversionTest CR\")\n\n\t\tBy(\"waiting for the ConversionTest CR to appear\")\n\t\tEventually(func(g Gomega) {\n\t\t\t_, getErr := kbc.Kubectl.Get(true, \"conversiontest\", \"conversiontest-sample\")\n\t\t\tg.Expect(getErr).NotTo(HaveOccurred(), \"expected the ConversionTest CR to exist\")\n\t\t}, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\t\tBy(\"validating that the converted resource in v2 has replicas == 3\")\n\t\tEventually(func(g Gomega) {\n\t\t\tout, getErr := kbc.Kubectl.Get(\n\t\t\t\ttrue,\n\t\t\t\t\"conversiontest\", \"conversiontest-sample\",\n\t\t\t\t\"-o\", \"jsonpath={.spec.replicas}\",\n\t\t\t)\n\t\t\tg.Expect(getErr).NotTo(HaveOccurred(), \"failed to get converted resource in v2\")\n\t\t\treplicas, atoiErr := strconv.Atoi(out)\n\t\t\tg.Expect(atoiErr).NotTo(HaveOccurred(), \"replicas field is not an integer\")\n\t\t\tg.Expect(replicas).To(Equal(3), \"expected replicas to be 3 after conversion\")\n\t\t}, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\t\tif opts.HasMetrics {\n\t\t\tBy(\"validating conversion metrics to confirm conversion operations\")\n\t\t\tmetricsOutput := GetMetricsOutput(controllerPodName, namePrefix, kbc)\n\t\t\tconversionMetric := `controller_runtime_reconcile_total{controller=\"conversiontest\",result=\"success\"} 1`\n\t\t\tExpect(metricsOutput).To(ContainSubstring(conversionMetric),\n\t\t\t\t\"Expected metric for successful ConversionTest reconciliation\")\n\t\t}\n\t}\n\n\t// Validate namespace-scoped behavior: operator should NOT reconcile resources outside its namespace\n\tif opts.IsNamespaced {\n\t\tBy(\"validating that namespace-scoped operator does not reconcile resources outside its namespace\")\n\n\t\t// Create a test namespace outside the operator's watch namespace\n\t\ttestNamespace := \"test-out-of-scope\"\n\t\t_, err = kbc.Kubectl.Command(\"create\", \"namespace\", testNamespace)\n\t\tExpect(err).NotTo(HaveOccurred(), \"test namespace should be created successfully\")\n\n\t\tBy(\"creating a CR in the out-of-scope namespace\")\n\t\t// Apply the same sample CR but in the test namespace\n\t\t_, err = kbc.Kubectl.Apply(false, \"-n\", testNamespace, \"-f\", sampleFile)\n\t\tExpect(err).NotTo(HaveOccurred(), \"CR should be created in test namespace\")\n\n\t\t// Wait a bit to ensure the controller would have time to reconcile if it was watching\n\t\ttime.Sleep(5 * time.Second)\n\n\t\tBy(\"verifying the CR was NOT reconciled (no status conditions set)\")\n\t\t// Get the CR and check if it has been reconciled by looking at its status\n\t\tcrName := strings.ToLower(kbc.Kind) + \"-sample\"\n\t\tcrOutput, err := kbc.Kubectl.Get(false, \"-n\", testNamespace,\n\t\t\tstrings.ToLower(kbc.Kind), crName,\n\t\t\t\"-o\", \"jsonpath={.status}\")\n\t\tExpect(err).NotTo(HaveOccurred(), \"CR should exist in test namespace\")\n\n\t\t// The status should be empty or not contain conditions set by the controller\n\t\t// because the namespace-scoped operator should not be watching this namespace\n\t\tExpect(crOutput).To(Or(\n\t\t\tBeEmpty(),\n\t\t\tNot(ContainSubstring(\"conditions\")),\n\t\t), \"CR in out-of-scope namespace should not have been reconciled by the controller\")\n\n\t\tBy(\"cleaning up the test namespace\")\n\t\t_, err = kbc.Kubectl.Command(\"delete\", \"namespace\", testNamespace, \"--timeout=60s\")\n\t\tExpect(err).NotTo(HaveOccurred(), \"test namespace should be deleted successfully\")\n\t}\n}\n"
  },
  {
    "path": "test/e2e/internal/helpers/plugin_test_metrics.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage helpers\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\" //nolint:staticcheck\n\t. \"github.com/onsi/gomega\"    //nolint:staticcheck\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n\t\"sigs.k8s.io/kubebuilder/v4/test/e2e/utils\"\n)\n\nconst (\n\ttokenRequestRawString = `{\"apiVersion\": \"authentication.k8s.io/v1\", \"kind\": \"TokenRequest\"}`\n)\n\n// tokenRequest is a trimmed down version of the authentication.k8s.io/v1/TokenRequest Type\n// that we want to use for extracting the token.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n\n// GetControllerPodName validates that the controller-manager pod is running and returns its name\nfunc GetControllerPodName(kbc *utils.TestContext) string {\n\tBy(\"validating that the controller-manager pod is running as expected\")\n\tvar controllerPodName string\n\tverifyControllerUp := func(g Gomega) error {\n\t\t// Get pod name\n\t\tpodOutput, err := kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\"-o\", \"go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}\"+\n\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tpodNames := util.GetNonEmptyLines(podOutput)\n\t\tif len(podNames) != 1 {\n\t\t\treturn fmt.Errorf(\"expect 1 controller pods running, but got %d\", len(podNames))\n\t\t}\n\t\tcontrollerPodName = podNames[0]\n\t\tg.Expect(controllerPodName).Should(ContainSubstring(\"controller-manager\"))\n\n\t\t// Validate pod status\n\t\tstatus, err := kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tif status != \"Running\" {\n\t\t\treturn fmt.Errorf(\"controller pod in %s status\", status)\n\t\t}\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\tout, err := kbc.Kubectl.CommandInNamespace(\"describe\", \"all\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\t_, _ = fmt.Fprintln(GinkgoWriter, out)\n\t}()\n\tEventually(verifyControllerUp, 5*time.Minute, time.Second).Should(Succeed())\n\treturn controllerPodName\n}\n\n// GetMetricsOutput returns the metrics output from curl pod\n// namePrefix is the prefix for service names (e.g., \"e2e-{suffix}\" or \"custom-operator\" from fullnameOverride)\nfunc GetMetricsOutput(controllerPodName, namePrefix string, kbc *utils.TestContext) string {\n\tvar err error\n\t// All Kubebuilder projects are cluster-scoped, so use ClusterRoleBinding\n\t_, err = kbc.Kubectl.Command(\n\t\t\"get\", \"clusterrolebinding\", fmt.Sprintf(\"metrics-%s\", kbc.TestSuffix),\n\t)\n\tif err != nil && strings.Contains(err.Error(), \"NotFound\") {\n\t\t_, err = kbc.Kubectl.Command(\n\t\t\t\"create\", \"clusterrolebinding\", fmt.Sprintf(\"metrics-%s\", kbc.TestSuffix),\n\t\t\tfmt.Sprintf(\"--clusterrole=%s-metrics-reader\", namePrefix),\n\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount),\n\t\t)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t} else {\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to check clusterrolebinding existence\")\n\t}\n\n\ttoken, err := serviceAccountToken(kbc)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(token).NotTo(BeEmpty())\n\n\tvar metricsOutput string\n\tBy(\"validating that the controller-manager service is available\")\n\t_, err = kbc.Kubectl.Get(\n\t\ttrue,\n\t\t\"service\", fmt.Sprintf(\"%s-controller-manager-metrics-service\", namePrefix),\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Controller-manager service should exist\")\n\n\tBy(\"ensuring the service endpoint is ready\")\n\tmetricsServiceName := fmt.Sprintf(\"%s-controller-manager-metrics-service\", namePrefix)\n\tcheckServiceEndpoint := func(g Gomega) {\n\t\tvar output string\n\t\toutput, err = kbc.Kubectl.Command(\n\t\t\t\"get\", \"endpointslices.discovery.k8s.io\",\n\t\t\t\"-n\", kbc.Kubectl.Namespace,\n\t\t\t\"-l\", fmt.Sprintf(\"kubernetes.io/service-name=%s\", metricsServiceName),\n\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\",\n\t\t)\n\t\tg.Expect(err).NotTo(HaveOccurred(), \"endpointslices should exist\")\n\t\tg.Expect(output).ShouldNot(BeEmpty(), \"no endpoints found\")\n\t}\n\tEventually(checkServiceEndpoint, 2*time.Minute, time.Second).Should(Succeed(),\n\t\t\"Service endpoint should be ready\")\n\n\t// NOTE: On Kubernetes 1.33+, we've observed a delay before the metrics endpoint becomes available\n\t// when using controller-runtime's WithAuthenticationAndAuthorization() with self-signed certificates.\n\t// This delay appears to stem from Kubernetes itself, potentially due to changes in how it initializes\n\t// service account tokens or handles TLS/service readiness.\n\tBy(\"ensuring the controller pod is fully ready before creating test pods\")\n\tverifyControllerPodReady := func(g Gomega) {\n\t\tvar output string\n\t\toutput, err = kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pod\", controllerPodName,\n\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\",\n\t\t)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t}\n\tEventually(verifyControllerPodReady, defaultTimeout, defaultPollingInterval).Should(Succeed())\n\n\twebhookServiceName := fmt.Sprintf(\"%s-webhook-service\", namePrefix)\n\tif _, err = kbc.Kubectl.Get(false, \"service\", webhookServiceName); err == nil {\n\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\tcheckWebhookEndpoint := func(g Gomega) {\n\t\t\tvar output string\n\t\t\toutput, err = kbc.Kubectl.Command(\n\t\t\t\t\"get\", \"endpointslices.discovery.k8s.io\",\n\t\t\t\t\"-n\", kbc.Kubectl.Namespace,\n\t\t\t\t\"-l\", fmt.Sprintf(\"kubernetes.io/service-name=%s\", webhookServiceName),\n\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\",\n\t\t\t)\n\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"webhook endpoints should exist\")\n\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"webhook endpoints not yet ready\")\n\t\t}\n\t\tEventually(checkWebhookEndpoint, defaultTimeout, defaultPollingInterval).Should(Succeed(),\n\t\t\t\"Webhook service endpoints should be ready\")\n\t}\n\n\tBy(\"creating a curl pod to access the metrics endpoint\")\n\tcmdOpts := cmdOptsToCreateCurlPod(namePrefix, kbc, token)\n\t_, err = kbc.Kubectl.CommandInNamespace(cmdOpts...)\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"validating that the curl pod is running as expected\")\n\tverifyCurlUp := func(g Gomega) {\n\t\tvar status string\n\t\tstatus, err = kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pods\", \"curl\", \"-o\", \"jsonpath={.status.phase}\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(status).To(Equal(\"Succeeded\"), fmt.Sprintf(\"curl pod in %s status\", status))\n\t}\n\tEventually(verifyCurlUp, 240*time.Second, time.Second).Should(Succeed())\n\n\tBy(\"validating that the correct ServiceAccount is being used\")\n\tsaName := kbc.Kubectl.ServiceAccount\n\tcurrentSAOutput, err := kbc.Kubectl.Get(\n\t\ttrue,\n\t\t\"serviceaccount\", saName,\n\t\t\"-o\", \"jsonpath={.metadata.name}\",\n\t)\n\tExpect(err).NotTo(HaveOccurred(), \"Failed to fetch the service account\")\n\tExpect(currentSAOutput).To(Equal(saName), \"The ServiceAccount in use does not match the expected one\")\n\n\tBy(\"validating that the metrics endpoint is serving as expected\")\n\tgetCurlLogs := func(g Gomega) {\n\t\tmetricsOutput, err = kbc.Kubectl.Logs(\"curl\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(metricsOutput).Should(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t}\n\tEventually(getCurlLogs, 10*time.Second, time.Second).Should(Succeed())\n\tremoveCurlPod(kbc)\n\treturn metricsOutput\n}\n\n// ValidateMetricsUnavailable validates that metrics are not exposed\n// namePrefix is the prefix for service names (e.g., \"e2e-{suffix}\" or \"custom-operator\" from fullnameOverride)\nfunc ValidateMetricsUnavailable(namePrefix string, kbc *utils.TestContext) {\n\t// All Kubebuilder projects are cluster-scoped, so use ClusterRoleBinding\n\t_, err := kbc.Kubectl.Command(\n\t\t\"create\", \"clusterrolebinding\", fmt.Sprintf(\"metrics-%s\", kbc.TestSuffix),\n\t\tfmt.Sprintf(\"--clusterrole=%s-metrics-reader\", namePrefix),\n\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount))\n\tExpect(err).NotTo(HaveOccurred())\n\n\ttoken, err := serviceAccountToken(kbc)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(token).NotTo(BeEmpty())\n\n\tBy(\"creating a curl pod to access the metrics endpoint\")\n\tcmdOpts := cmdOptsToCreateCurlPod(namePrefix, kbc, token)\n\t_, err = kbc.Kubectl.CommandInNamespace(cmdOpts...)\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"validating that the curl pod fail as expected\")\n\tverifyCurlUp := func(g Gomega) {\n\t\tstatus, errCurl := kbc.Kubectl.Get(\n\t\t\ttrue,\n\t\t\t\"pods\", \"curl\", \"-o\", \"jsonpath={.status.phase}\")\n\t\tg.Expect(errCurl).NotTo(HaveOccurred())\n\t\tg.Expect(status).NotTo(Equal(\"Failed\"),\n\t\t\tfmt.Sprintf(\"curl pod in %s status when should fail with an error\", status))\n\t}\n\tEventually(verifyCurlUp, 240*time.Second, time.Second).Should(Succeed())\n\n\tBy(\"validating that the metrics endpoint is not working as expected\")\n\tgetCurlLogs := func(g Gomega) {\n\t\tmetricsOutput, err := kbc.Kubectl.Logs(\"curl\")\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tg.Expect(metricsOutput).Should(ContainSubstring(\"Could not resolve host\"))\n\t}\n\tEventually(getCurlLogs, 10*time.Second, time.Second).Should(Succeed())\n\tremoveCurlPod(kbc)\n}\n\nfunc cmdOptsToCreateCurlPod(namePrefix string, kbc *utils.TestContext, token string) []string {\n\tcmdOpts := []string{\n\t\t\"run\", \"curl\",\n\t\t\"--restart=Never\",\n\t\t\"--namespace\", kbc.Kubectl.Namespace,\n\t\t\"--image=curlimages/curl:latest\",\n\t\t\"--overrides\",\n\t\tfmt.Sprintf(`{\n\t\t\t\"spec\": {\n\t\t\t\t\"containers\": [{\n\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\"for i in $(seq 1 30); do `+\n\t\t\t`curl -v -k -H 'Authorization: Bearer %s' `+\n\t\t\t`https://%s-controller-manager-metrics-service.%s.svc.cluster.local:8443/metrics `+\n\t\t\t`&& exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t],\n\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}],\n\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t}\n    }`, token, namePrefix, kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount),\n\t}\n\treturn cmdOpts\n}\n\nfunc removeCurlPod(kbc *utils.TestContext) {\n\tBy(\"cleaning up the curl pod\")\n\t_, err := kbc.Kubectl.Delete(true, \"pods/curl\", \"--grace-period=0\", \"--force\")\n\tExpect(err).NotTo(HaveOccurred())\n}\n\n// serviceAccountToken provides a helper function that can provide you with a service account\n// token that you can use to interact with the service. This function leverages the k8s'\n// TokenRequest API in raw format in order to make it generic for all version of the k8s that\n// is currently being supported in kubebuilder test infra.\n// TokenRequest API returns the token in raw JWT format itself. There is no conversion required.\nfunc serviceAccountToken(kbc *utils.TestContext) (string, error) {\n\tvar out string\n\n\tsecretName := fmt.Sprintf(\"%s-token-request\", kbc.Kubectl.ServiceAccount)\n\ttokenRequestFile := filepath.Join(kbc.Dir, secretName)\n\tif err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o755)); err != nil {\n\t\treturn out, fmt.Errorf(\"error creating token request file %s: %w\", tokenRequestFile, err)\n\t}\n\tgetToken := func(g Gomega) {\n\t\t// Output of this is already a valid JWT token. No need to covert this from base64 to string format\n\t\trawJSON, err := kbc.Kubectl.Command(\n\t\t\t\"create\",\n\t\t\t\"--raw\", fmt.Sprintf(\n\t\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\t\tkbc.Kubectl.Namespace,\n\t\t\t\tkbc.Kubectl.ServiceAccount,\n\t\t\t),\n\t\t\t\"-f\", tokenRequestFile,\n\t\t)\n\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal([]byte(rawJSON), &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(getToken, 2*time.Minute, time.Second).Should(Succeed())\n\n\treturn out, nil\n}\n"
  },
  {
    "path": "test/e2e/kind-config.yaml",
    "content": "#  Copyright 2020 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnetworking:\n  disableDefaultCNI: false # Let it use default CNI so we can test NetworkPolicies\nnodes:\n  - role: control-plane\n"
  },
  {
    "path": "test/e2e/local.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\nsource \"$(dirname \"$0\")/setup.sh\"\n\nexport KIND_CLUSTER=\"local-kubebuilder-e2e\"\ncreate_cluster ${KIND_K8S_VERSION}\nif [ -z \"${SKIP_KIND_CLEANUP:-}\" ]; then\n  trap delete_cluster EXIT\nfi\n\n# Wait a moment for the cluster control plane to be fully initialized\necho \"Ensuring cluster is fully initialized...\"\nsleep 5\n\n# Export kubeconfig for the cluster\nkind export kubeconfig --kubeconfig $tmp_root/kubeconfig --name $KIND_CLUSTER\nexport KUBECONFIG=$tmp_root/kubeconfig\n\n# Verify we can communicate with the cluster\nkubectl cluster-info\necho \"Cluster is ready for testing\"\n\ntest_cluster -v -ginkgo.v\n"
  },
  {
    "path": "test/e2e/setup.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nbuild_kb\nfetch_tools\ninstall_kind\n\n# Creates a named kind cluster given a k8s version.\n# The KIND_CLUSTER variable defines the cluster name and\n# is expected to be defined in the calling environment.\n#\n# Usage:\n#\n#   export KIND_CLUSTER=<kind cluster name>\n#   create_cluster <k8s version>\nfunction create_cluster {\n  echo \"Getting kind config...\"\n  KIND_VERSION=$1\n  : ${KIND_CLUSTER:?\"KIND_CLUSTER must be set\"}\n  : ${1:?\"k8s version must be set as arg 1\"}\n  if ! kind get clusters | grep -q $KIND_CLUSTER ; then\n    version_prefix=\"${KIND_VERSION%.*}\"\n    kind_config=$(dirname \"$0\")/kind-config.yaml\n    if test -f $(dirname \"$0\")/kind-config-${version_prefix}.yaml; then\n      kind_config=$(dirname \"$0\")/kind-config-${version_prefix}.yaml\n    fi\n    echo \"Creating cluster...\"\n    kind create cluster -v 4 --name $KIND_CLUSTER --retain --wait=5m --config ${kind_config} --image=kindest/node:$1\n    \n    echo \"Waiting for cluster to be fully ready...\"\n    kubectl wait --for=condition=Ready nodes --all --timeout=5m\n  fi\n}\n\n# Deletes a kind cluster by cluster name.\n# The KIND_CLUSTER variable defines the cluster name and\n# is expected to be defined in the calling environment.\n#\n# Usage:\n#\n#   export KIND_CLUSTER=<kind cluster name>\n#   delete_cluster\nfunction delete_cluster {\n  : ${KIND_CLUSTER:?\"KIND_CLUSTER must be set\"}\n  kind delete cluster --name $KIND_CLUSTER\n}\n\nfunction test_cluster {\n  local flags=\"$@\"\n\n  # Detect the platform architecture for the kind cluster\n  # Kind clusters now run natively on the host architecture (arm64 on Apple Silicon, amd64 on x86)\n  local kind_platform=\"linux/amd64\"\n  if [[ \"$OSTYPE\" == \"darwin\"* ]] && [[ \"$(uname -m)\" == \"arm64\" ]]; then\n    kind_platform=\"linux/arm64\"\n  elif [[ \"$(uname -m)\" == \"aarch64\" ]]; then\n    kind_platform=\"linux/arm64\"\n  fi\n\n  # Pull images for the correct platform\n  docker pull --platform ${kind_platform} memcached:1.6.26-alpine3.19\n  docker pull --platform ${kind_platform} busybox:1.36.1\n  docker pull --platform ${kind_platform} bitnami/kubectl:latest\n\n  # Load images directly with ctr to avoid kind's --all-platforms issue\n  # kind load docker-image uses --all-platforms internally which breaks with multi-platform manifests\n  docker save memcached:1.6.26-alpine3.19 | docker exec -i $KIND_CLUSTER-control-plane ctr --namespace=k8s.io images import /dev/stdin\n  \n  # Busybox has Docker save issues on some platforms, pull directly as fallback\n  if ! docker save busybox:1.36.1 2>/dev/null | docker exec -i $KIND_CLUSTER-control-plane ctr --namespace=k8s.io images import /dev/stdin 2>/dev/null; then\n    docker exec $KIND_CLUSTER-control-plane ctr --namespace=k8s.io images pull --platform ${kind_platform} docker.io/library/busybox:1.36.1 >/dev/null 2>&1\n  fi\n\n  go test $(dirname \"$0\")/all $flags -timeout 60m\n\n  docker save bitnami/kubectl:latest | docker exec -i $KIND_CLUSTER-control-plane ctr --namespace=k8s.io images import /dev/stdin\n}\n"
  },
  {
    "path": "test/e2e/utils/kubectl.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Kubectl contains context to run kubectl commands\ntype Kubectl struct {\n\t*CmdContext\n\tNamespace      string\n\tServiceAccount string\n}\n\n// Command is a general func to run kubectl commands\nfunc (k *Kubectl) Command(cmdOptions ...string) (string, error) {\n\tcmd := exec.Command(\"kubectl\", cmdOptions...)\n\toutput, err := k.Run(cmd)\n\treturn string(output), err\n}\n\n// WithInput is a general func to run kubectl commands with input\nfunc (k *Kubectl) WithInput(stdinInput string) *Kubectl {\n\tk.Stdin = strings.NewReader(stdinInput)\n\treturn k\n}\n\n// CommandInNamespace is a general func to run kubectl commands in the namespace\nfunc (k *Kubectl) CommandInNamespace(cmdOptions ...string) (string, error) {\n\tif len(k.Namespace) == 0 {\n\t\treturn \"\", errors.New(\"namespace should not be empty\")\n\t}\n\treturn k.Command(append([]string{\"-n\", k.Namespace}, cmdOptions...)...)\n}\n\n// Apply is a general func to run kubectl apply commands\nfunc (k *Kubectl) Apply(inNamespace bool, cmdOptions ...string) (string, error) {\n\tops := append([]string{\"apply\"}, cmdOptions...)\n\tif inNamespace {\n\t\treturn k.CommandInNamespace(ops...)\n\t}\n\treturn k.Command(ops...)\n}\n\n// Get is a func to run kubectl get commands\nfunc (k *Kubectl) Get(inNamespace bool, cmdOptions ...string) (string, error) {\n\tops := append([]string{\"get\"}, cmdOptions...)\n\tif inNamespace {\n\t\treturn k.CommandInNamespace(ops...)\n\t}\n\treturn k.Command(ops...)\n}\n\n// Delete is a func to run kubectl delete commands\nfunc (k *Kubectl) Delete(inNamespace bool, cmdOptions ...string) (string, error) {\n\tops := append([]string{\"delete\"}, cmdOptions...)\n\tif inNamespace {\n\t\treturn k.CommandInNamespace(ops...)\n\t}\n\treturn k.Command(ops...)\n}\n\n// Logs is a func to run kubectl logs commands\nfunc (k *Kubectl) Logs(cmdOptions ...string) (string, error) {\n\tops := append([]string{\"logs\"}, cmdOptions...)\n\treturn k.CommandInNamespace(ops...)\n}\n\n// Wait is a func to run kubectl wait commands\nfunc (k *Kubectl) Wait(inNamespace bool, cmdOptions ...string) (string, error) {\n\tops := append([]string{\"wait\"}, cmdOptions...)\n\tif inNamespace {\n\t\treturn k.CommandInNamespace(ops...)\n\t}\n\treturn k.Command(ops...)\n}\n\n// VersionInfo holds a subset of client/server version information.\ntype VersionInfo struct {\n\tMajor      string `json:\"major\"`\n\tMinor      string `json:\"minor\"`\n\tGitVersion string `json:\"gitVersion\"`\n\n\t// Leaving major/minor int fields unexported prevents them from being set\n\t// while leaving their exported counterparts untouched -> incorrect marshaled format.\n\tmajor, minor uint64\n}\n\n// GetMajorInt returns the uint64 representation of vi.Major.\nfunc (vi VersionInfo) GetMajorInt() uint64 { return vi.major }\n\n// GetMinorInt returns the uint64 representation of vi.Minor.\nfunc (vi VersionInfo) GetMinorInt() uint64 { return vi.minor }\n\nfunc (vi *VersionInfo) parseVersionInts() (err error) {\n\tif vi.Major != \"\" {\n\t\tif vi.major, err = strconv.ParseUint(vi.Major, 10, 64); err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing major version %q: %w\", vi.Major, err)\n\t\t}\n\t}\n\tif vi.Minor != \"\" {\n\t\tif vi.minor, err = strconv.ParseUint(vi.Minor, 10, 64); err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing minor version %q: %w\", vi.Minor, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// KubernetesVersion holds a subset of both client and server versions.\ntype KubernetesVersion struct {\n\tClientVersion VersionInfo `json:\"clientVersion,omitempty\"`\n\tServerVersion VersionInfo `json:\"serverVersion,omitempty\"`\n}\n\nfunc (v *KubernetesVersion) prepare() error {\n\tif err := v.ClientVersion.parseVersionInts(); err != nil {\n\t\treturn err\n\t}\n\treturn v.ServerVersion.parseVersionInts()\n}\n\n// Version is a func to run kubectl version command\nfunc (k *Kubectl) Version() (ver KubernetesVersion, err error) {\n\tout, err := k.Command(\"version\", \"-o\", \"json\")\n\tif err != nil {\n\t\treturn KubernetesVersion{}, fmt.Errorf(\"error getting kubernetes version: %w\", err)\n\t}\n\tif decodeErr := ver.decode(out); decodeErr != nil {\n\t\treturn KubernetesVersion{}, fmt.Errorf(\"error parsing kubernetes version: %w\", decodeErr)\n\t}\n\treturn ver, nil\n}\n\nfunc (v *KubernetesVersion) decode(out string) error {\n\tdec := json.NewDecoder(strings.NewReader(out))\n\tif err := dec.Decode(&v); err != nil {\n\t\treturn fmt.Errorf(\"error decoding kubernetes version: %w\", err)\n\t}\n\treturn v.prepare()\n}\n"
  },
  {
    "path": "test/e2e/utils/kubectl_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Kubectl\", func() {\n\tvar ver KubernetesVersion\n\tAfterEach(func() {\n\t\tver = KubernetesVersion{}\n\t})\n\tContext(\"successful 'kubectl version' output\", func() {\n\t\tIt(\"decodes both client and server versions\", func() {\n\t\t\tExpect(ver.decode(clientServerOutput)).To(Succeed())\n\t\t\tExpect(ver.ClientVersion.major).To(BeNumerically(\"==\", 1))\n\t\t\tExpect(ver.ClientVersion.minor).To(BeNumerically(\"==\", 21))\n\t\t\tExpect(ver.ServerVersion.major).To(BeNumerically(\"==\", 1))\n\t\t\tExpect(ver.ServerVersion.minor).To(BeNumerically(\"==\", 21))\n\t\t})\n\t\tIt(\"decodes only client version\", func() {\n\t\t\tExpect(ver.decode(clientOnlyOutput)).To(Succeed())\n\t\t\tExpect(ver.ClientVersion.major).To(BeNumerically(\"==\", 1))\n\t\t\tExpect(ver.ClientVersion.minor).To(BeNumerically(\"==\", 21))\n\t\t\tExpect(ver.ServerVersion.major).To(BeNumerically(\"==\", 0))\n\t\t\tExpect(ver.ServerVersion.minor).To(BeNumerically(\"==\", 0))\n\t\t})\n\t})\n\tContext(\"'kubectl version' output with non-JSON text\", func() {\n\t\tIt(\"handles warning logs\", func() {\n\t\t\tExpect(ver.decode(clientServerWithWarningOutput)).To(Succeed())\n\t\t\tExpect(ver.ClientVersion.major).To(BeNumerically(\"==\", 1))\n\t\t\tExpect(ver.ClientVersion.minor).To(BeNumerically(\"==\", 21))\n\t\t\tExpect(ver.ServerVersion.major).To(BeNumerically(\"==\", 1))\n\t\t\tExpect(ver.ServerVersion.minor).To(BeNumerically(\"==\", 21))\n\t\t})\n\t})\n\tContext(\"with error text\", func() {\n\t\tIt(\"returns an error\", func() {\n\t\t\tExpect(ver.decode(errorOutput)).NotTo(Succeed())\n\t\t})\n\t})\n})\n\nconst clientServerOutput = `\n{\n  \"clientVersion\": {\n    \"major\": \"1\",\n    \"minor\": \"21\",\n    \"gitVersion\": \"v0.21.0-beta.1\",\n    \"gitCommit\": \"0d10c3f72592addce965b9bb34992eb6fc283a3b\",\n    \"gitTreeState\": \"clean\",\n    \"buildDate\": \"2021-08-31T22:03:33Z\",\n    \"goVersion\": \"go1.16.6\",\n    \"compiler\": \"gc\",\n    \"platform\": \"linux/amd64\"\n  },\n  \"serverVersion\": {\n    \"major\": \"1\",\n    \"minor\": \"21\",\n    \"gitVersion\": \"v1.21.1\",\n    \"gitCommit\": \"5e58841cce77d4bc13713ad2b91fa0d961e69192\",\n    \"gitTreeState\": \"clean\",\n    \"buildDate\": \"2021-05-18T01:10:20Z\",\n    \"goVersion\": \"go1.16.4\",\n    \"compiler\": \"gc\",\n    \"platform\": \"linux/amd64\"\n  }\n}\n`\n\nconst clientOnlyOutput = `\n{\n  \"clientVersion\": {\n    \"major\": \"1\",\n    \"minor\": \"21\",\n    \"gitVersion\": \"v0.21.0-beta.1\",\n    \"gitCommit\": \"0d10c3f72592addce965b9bb34992eb6fc283a3b\",\n    \"gitTreeState\": \"clean\",\n    \"buildDate\": \"2021-08-31T22:03:33Z\",\n    \"goVersion\": \"go1.16.6\",\n    \"compiler\": \"gc\",\n    \"platform\": \"linux/amd64\"\n  }\n}\n`\n\nconst clientServerWithWarningOutput = `\n{\n  \"clientVersion\": {\n    \"major\": \"1\",\n    \"minor\": \"21\",\n    \"gitVersion\": \"v0.21.0-beta.1\",\n    \"gitCommit\": \"0d10c3f72592addce965b9bb34992eb6fc283a3b\",\n    \"gitTreeState\": \"clean\",\n    \"buildDate\": \"2021-08-31T22:03:33Z\",\n    \"goVersion\": \"go1.16.6\",\n    \"compiler\": \"gc\",\n    \"platform\": \"linux/amd64\"\n  },\n  \"serverVersion\": {\n    \"major\": \"1\",\n    \"minor\": \"21\",\n    \"gitVersion\": \"v1.21.1\",\n    \"gitCommit\": \"5e58841cce77d4bc13713ad2b91fa0d961e69192\",\n    \"gitTreeState\": \"clean\",\n    \"buildDate\": \"2021-05-18T01:10:20Z\",\n    \"goVersion\": \"go1.16.4\",\n    \"compiler\": \"gc\",\n    \"platform\": \"linux/amd64\"\n  }\n}\nWARNING: version difference between client (0.21) and server (1.21) exceeds the supported minor version skew of +/-1\n`\n\nconst errorOutput = `\nERROR: reason blah blah\n`\n"
  },
  {
    "path": "test/e2e/utils/suite_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestUtils(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"Utils Suite\")\n}\n"
  },
  {
    "path": "test/e2e/utils/test_context.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\tlog \"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t//nolint:staticcheck\n\t. \"github.com/onsi/ginkgo/v2\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindCluster = \"kind\"\n\tdefaultKindBinary  = \"kind\"\n\n\tprometheusOperatorVersion = \"v0.89.0\"\n\tprometheusOperatorURL     = \"https://github.com/prometheus-operator/prometheus-operator/\" +\n\t\t\"releases/download/%s/bundle.yaml\"\n)\n\n// TestContext specified to run e2e tests\ntype TestContext struct {\n\t*CmdContext\n\tTestSuffix   string\n\tDomain       string\n\tGroup        string\n\tVersion      string\n\tKind         string\n\tResources    string\n\tImageName    string\n\tBinaryName   string\n\tKubectl      *Kubectl\n\tK8sVersion   *KubernetesVersion\n\tIsRestricted bool\n}\n\n// NewTestContext init with a random suffix for test TestContext stuff,\n// to avoid conflict when running tests synchronously.\nfunc NewTestContext(binaryName string, env ...string) (*TestContext, error) {\n\ttestSuffix, err := util.RandomSuffix()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to generate random suffix: %w\", err)\n\t}\n\n\tcc := &CmdContext{\n\t\tEnv: env,\n\t}\n\n\t// Use kubectl to get Kubernetes client and cluster version.\n\tkubectl := &Kubectl{\n\t\tNamespace:      fmt.Sprintf(\"e2e-%s-system\", testSuffix),\n\t\tServiceAccount: fmt.Sprintf(\"e2e-%s-controller-manager\", testSuffix),\n\t\tCmdContext:     cc,\n\t}\n\n\t// For test outside of cluster we do not need to have kubectl\n\tvar k8sVersion *KubernetesVersion\n\tfakeVersion := &KubernetesVersion{\n\t\tClientVersion: VersionInfo{\n\t\t\tMajor:      \"1\",\n\t\t\tMinor:      \"0\",\n\t\t\tGitVersion: \"v1.0.0-fake\",\n\t\t},\n\t\tServerVersion: VersionInfo{\n\t\t\tMajor:      \"1\",\n\t\t\tMinor:      \"0\",\n\t\t\tGitVersion: \"v1.0.0-fake\",\n\t\t},\n\t}\n\n\tvar v KubernetesVersion\n\tvar lookupErr error\n\n\t_, lookupErr = exec.LookPath(\"kubectl\")\n\tif lookupErr != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: kubectl not found in PATH; proceeding with fake version\\n\")\n\t\tk8sVersion = fakeVersion\n\t} else if v, err = kubectl.Version(); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: failed to get kubernetes version: %v\\n\", err)\n\t\tk8sVersion = fakeVersion\n\t} else {\n\t\tk8sVersion = &v\n\t}\n\t// Set CmdContext.Dir after running Kubectl.Version() because dir does not exist yet.\n\tif cc.Dir, err = filepath.Abs(\"e2e-\" + testSuffix); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to determine absolute path to %q: %w\", \"e2e-\"+testSuffix, err)\n\t}\n\n\treturn &TestContext{\n\t\tTestSuffix: testSuffix,\n\t\tDomain:     \"example.com\" + testSuffix,\n\t\tGroup:      \"bar\" + testSuffix,\n\t\tVersion:    \"v1alpha1\",\n\t\tKind:       \"Foo\" + testSuffix,\n\t\tResources:  \"foo\" + testSuffix + \"s\",\n\t\tImageName:  \"e2e-test/controller-manager:\" + testSuffix,\n\t\tCmdContext: cc,\n\t\tKubectl:    kubectl,\n\t\tK8sVersion: k8sVersion,\n\t\tBinaryName: binaryName,\n\t}, nil\n}\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Prepare prepares the test environment.\nfunc (t *TestContext) Prepare() error {\n\t// Remove tools used by projects in the environment so the correct version is downloaded for each test.\n\t_, _ = fmt.Fprintln(GinkgoWriter, \"cleaning up tools\")\n\tfor _, toolName := range []string{\"controller-gen\", \"kustomize\"} {\n\t\tif toolPath, err := exec.LookPath(toolName); err == nil {\n\t\t\tif err := os.RemoveAll(toolPath); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to remove %q: %w\", toolName, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"preparing testing directory: %s\\n\", t.Dir)\n\tif err := os.MkdirAll(t.Dir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"error creating test directory %q: %w\", t.Dir, err)\n\t}\n\n\treturn nil\n}\n\n// makeCertManagerURL returns a kubectl-able URL for the cert-manager bundle.\nfunc (t *TestContext) makeCertManagerURL() string {\n\treturn fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n}\n\nfunc (t *TestContext) makePrometheusOperatorURL() string {\n\treturn fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc (t *TestContext) InstallCertManager() error {\n\turl := t.makeCertManagerURL()\n\tif _, err := t.Kubectl.Apply(false, \"-f\", url, \"--validate=false\"); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tif _, err := t.Kubectl.Wait(false, \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t); err != nil {\n\t\treturn err\n\t}\n\n\t// Additional wait for webhook TLS to be fully initialized\n\t// The webhook needs time after deployment is ready to generate and serve valid certificates\n\ttime.Sleep(10 * time.Second)\n\treturn nil\n}\n\n// UninstallCertManager uninstalls the cert manager bundle.\nfunc (t *TestContext) UninstallCertManager() {\n\turl := t.makeCertManagerURL()\n\tif _, err := t.Kubectl.Delete(false, \"-f\", url); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// InstallPrometheusOperManager installs the prometheus manager bundle.\nfunc (t *TestContext) InstallPrometheusOperManager() error {\n\turl := t.makePrometheusOperatorURL()\n\t// Use server-side apply to handle large CRD annotations\n\t_, err := t.Kubectl.Command(\"apply\", \"--server-side\", \"-f\", url)\n\treturn err\n}\n\n// UninstallPrometheusOperManager uninstalls the prometheus manager bundle.\nfunc (t *TestContext) UninstallPrometheusOperManager() {\n\turl := t.makePrometheusOperatorURL()\n\tif _, err := t.Kubectl.Delete(false, \"-f\", url); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// Init is for running `kubebuilder init`\nfunc (t *TestContext) Init(initOptions ...string) error {\n\tinitOptions = append([]string{\"init\"}, initOptions...)\n\t//nolint:gosec\n\tcmd := exec.Command(t.BinaryName, initOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// Edit is for running `kubebuilder edit`\nfunc (t *TestContext) Edit(editOptions ...string) error {\n\teditOptions = append([]string{\"edit\"}, editOptions...)\n\t//nolint:gosec\n\tcmd := exec.Command(t.BinaryName, editOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// CreateAPI is for running `kubebuilder create api`\nfunc (t *TestContext) CreateAPI(resourceOptions ...string) error {\n\tresourceOptions = append([]string{\"create\", \"api\"}, resourceOptions...)\n\t//nolint:gosec\n\tcmd := exec.Command(t.BinaryName, resourceOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// CreateWebhook is for running `kubebuilder create webhook`\nfunc (t *TestContext) CreateWebhook(resourceOptions ...string) error {\n\tresourceOptions = append([]string{\"create\", \"webhook\"}, resourceOptions...)\n\t//nolint:gosec\n\tcmd := exec.Command(t.BinaryName, resourceOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// Regenerate is for running `kubebuilder alpha generate`\nfunc (t *TestContext) Regenerate(resourceOptions ...string) error {\n\tresourceOptions = append([]string{\"alpha\", \"generate\"}, resourceOptions...)\n\t//nolint:gosec\n\tcmd := exec.Command(t.BinaryName, resourceOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// Make is for running `make` with various targets\nfunc (t *TestContext) Make(makeOptions ...string) error {\n\tcmd := exec.Command(\"make\", makeOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// Tidy runs `go mod tidy` so that go 1.16 build doesn't fail.\n// See https://blog.golang.org/go116-module-changes#TOC_3.\nfunc (t *TestContext) Tidy() error {\n\tcmd := exec.Command(\"go\", \"mod\", \"tidy\")\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// Destroy is for cleaning up the docker images for testing\nfunc (t *TestContext) Destroy() {\n\t//nolint:gosec\n\t// if image name is not present or not provided skip execution of docker command\n\tif t.ImageName != \"\" {\n\t\t// Check white space from image name\n\t\tif len(strings.TrimSpace(t.ImageName)) == 0 {\n\t\t\tlog.Info(\"Image not set, skip cleaning up of docker image\")\n\t\t} else {\n\t\t\tcmd := exec.Command(\"docker\", \"rmi\", \"-f\", t.ImageName)\n\t\t\tif _, err := t.Run(cmd); err != nil {\n\t\t\t\twarnError(err)\n\t\t\t}\n\t\t}\n\t}\n\tif err := os.RemoveAll(t.Dir); err != nil {\n\t\twarnError(err)\n\t}\n}\n\n// CreateManagerNamespace will create the namespace where the manager is deployed\nfunc (t *TestContext) CreateManagerNamespace() error {\n\t_, err := t.Kubectl.Command(\"create\", \"ns\", t.Kubectl.Namespace)\n\treturn err\n}\n\n// LabelNamespacesToEnforceRestricted will label specified namespaces so that we can verify\n// if the manifests can be applied in restricted environments with strict security policy enforced\nfunc (t *TestContext) LabelNamespacesToEnforceRestricted() error {\n\t_, err := t.Kubectl.Command(\"label\", \"--overwrite\", \"ns\", t.Kubectl.Namespace,\n\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\treturn err\n}\n\n// RemoveNamespaceLabelToEnforceRestricted will remove the `pod-security.kubernetes.io/enforce` label\n// from the specified namespace\nfunc (t *TestContext) RemoveNamespaceLabelToEnforceRestricted() error {\n\t_, err := t.Kubectl.Command(\"label\", \"ns\", t.Kubectl.Namespace, \"pod-security.kubernetes.io/enforce-\")\n\treturn err\n}\n\n// LoadImageToKindCluster loads a local docker image to the kind cluster\nfunc (t *TestContext) LoadImageToKindCluster() error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", t.ImageName, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// LoadImageToKindClusterWithName loads a local docker image with the name informed to the kind cluster\nfunc (t TestContext) LoadImageToKindClusterWithName(image string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", \"--name\", cluster, image}\n\tkindBinary := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// CmdContext provides context for command execution\ntype CmdContext struct {\n\t// environment variables in k=v format.\n\tEnv   []string\n\tDir   string\n\tStdin io.Reader\n}\n\n// Run executes the provided command within this context\nfunc (cc *CmdContext) Run(cmd *exec.Cmd) ([]byte, error) {\n\tcmd.Dir = cc.Dir\n\tcmd.Env = append(os.Environ(), cc.Env...)\n\tcmd.Stdin = cc.Stdin\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %s\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn output, fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn output, nil\n}\n\n// AllowProjectBeMultiGroup will update the PROJECT file with the information to allow we scaffold\n// apis with different groups. be available.\nfunc (t *TestContext) AllowProjectBeMultiGroup() error {\n\tconst multiGroup = `multigroup: true\n`\n\tprojectBytes, err := os.ReadFile(filepath.Join(t.Dir, \"PROJECT\"))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot read project file: %w\", err)\n\t}\n\n\tprojectBytes = append([]byte(multiGroup), projectBytes...)\n\terr = os.WriteFile(filepath.Join(t.Dir, \"PROJECT\"), projectBytes, 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not write to project file: %w\", err)\n\t}\n\treturn nil\n}\n\n// InstallHelm installs Helm in the e2e server.\nfunc (t *TestContext) InstallHelm() error {\n\t// Check if Helm is already installed\n\tcheckCmd := exec.Command(\"helm\", \"version\")\n\t_, err := t.Run(checkCmd)\n\tif err == nil {\n\t\t// Helm is already installed, skip installation\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Helm is already installed, skipping installation\\n\")\n\t\treturn nil\n\t}\n\n\t// Install Helm if not found\n\thelmInstallScript := \"https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4\"\n\tcmd := exec.Command(\"bash\", \"-c\", fmt.Sprintf(\"curl -fsSL %s | bash\", helmInstallScript))\n\t_, err = t.Run(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tverifyCmd := exec.Command(\"helm\", \"version\")\n\t_, err = t.Run(verifyCmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// UninstallHelmRelease removes the specified Helm release from the cluster.\n// Uses the chart name (project name) as the release name, which is standard Helm practice.\n// Note: With crd.keep=true (default), CRDs are preserved after uninstall.\n// Use DeleteCRDs() to clean them up after verifying they persisted.\nfunc (t *TestContext) UninstallHelmRelease() error {\n\tns := fmt.Sprintf(\"e2e-%s-system\", t.TestSuffix)\n\treleaseName := fmt.Sprintf(\"e2e-%s\", t.TestSuffix)\n\tcmd := exec.Command(\"helm\", \"uninstall\", releaseName, \"--namespace\", ns)\n\n\t_, err := t.Run(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// EditHelmPlugin is for running `kubebuilder edit --plugins=helm.kubebuilder.io/v2-alpha`\nfunc (t *TestContext) EditHelmPlugin() error {\n\tcmd := exec.Command(t.BinaryName, \"edit\", \"--plugins=helm.kubebuilder.io/v2-alpha\")\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// HelmInstallRelease is for running `helm install`\n// Uses the chart name (project name) as the release name, which is standard Helm practice.\n// When release name matches chart name, chart.fullname simplifies to just the chart name,\n// preserving kustomize resource naming.\nfunc (t *TestContext) HelmInstallRelease() error {\n\treturn t.HelmInstallReleaseWithOptions(true) // Default: crd.keep=true\n}\n\n// HelmInstallReleaseWithOptions is for running `helm install` with configurable options\n// crdKeep controls whether CRDs are preserved on helm uninstall (helm.sh/resource-policy: keep)\nfunc (t *TestContext) HelmInstallReleaseWithOptions(crdKeep bool) error {\n\treleaseName := fmt.Sprintf(\"e2e-%s\", t.TestSuffix)\n\t// Set the image to match what was built (format: e2e-test/controller-manager:suffix)\n\t// Helm chart uses manager.image.repository and manager.image.tag\n\t// --create-namespace ensures the namespace exists before installing\n\tcmd := exec.Command(\"helm\", \"install\", releaseName, \"dist/chart\",\n\t\t\"--namespace\", fmt.Sprintf(\"e2e-%s-system\", t.TestSuffix),\n\t\t\"--create-namespace\",\n\t\t\"--set\", fmt.Sprintf(\"manager.image.repository=%s\", \"e2e-test/controller-manager\"),\n\t\t\"--set\", fmt.Sprintf(\"manager.image.tag=%s\", t.TestSuffix),\n\t\t\"--set\", fmt.Sprintf(\"crd.keep=%t\", crdKeep))\n\t_, err := t.Run(cmd)\n\treturn err\n}\n\n// HelmUpgradeReleaseWithReplicas runs `helm upgrade` with manager.replicas set.\n// Uses --reuse-values so existing image and other settings are preserved.\nfunc (t *TestContext) HelmUpgradeReleaseWithReplicas(replicas int) error {\n\treleaseName := fmt.Sprintf(\"e2e-%s\", t.TestSuffix)\n\tns := fmt.Sprintf(\"e2e-%s-system\", t.TestSuffix)\n\tcmd := exec.Command(\"helm\", \"upgrade\", releaseName, \"dist/chart\",\n\t\t\"--namespace\", ns,\n\t\t\"--reuse-values\",\n\t\t\"--set\", fmt.Sprintf(\"manager.replicas=%d\", replicas))\n\t_, err := t.Run(cmd)\n\treturn err\n}\n"
  },
  {
    "path": "test/e2e/utils/webhooks.go",
    "content": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util\"\n)\n\n// ImplementWebhooks will mock an webhook data\nfunc ImplementWebhooks(filename, _ string) error {\n\t//nolint:gosec // false positive\n\tbs, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading webhooks file %q: %w\", filename, err)\n\t}\n\tstr := string(bs)\n\n\tstr, err = util.EnsureExistAndReplace(\n\t\tstr,\n\t\t\"import (\",\n\t\t`import (\n\t\"errors\"`)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error replacing imports in webhooks file %q: %w\", filename, err)\n\t}\n\n\t// implement defaulting webhook logic\n\treplace := `if obj.Spec.Count == 0 {\n\t\tobj.Spec.Count = 5\n\t}`\n\tstr, err = util.EnsureExistAndReplace(\n\t\tstr,\n\t\t\"// TODO(user): fill in your defaulting logic.\",\n\t\treplace,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error replacing default logic in webhooks file %q: %w\", filename, err)\n\t}\n\n\t// implement validation webhook logic\n\tstr, err = util.EnsureExistAndReplace(\n\t\tstr,\n\t\t\"// TODO(user): fill in your validation logic upon object creation.\",\n\t\t`if obj.Spec.Count < 0 {\n\t\treturn nil, errors.New(\".spec.count must >= 0\")\n\t}`)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error replacing validation logic in webhooks file %q: %w\", filename, err)\n\t}\n\tstr, err = util.EnsureExistAndReplace(\n\t\tstr,\n\t\t\"// TODO(user): fill in your validation logic upon object update.\",\n\t\t`if newObj.Spec.Count < 0 {\n\t\treturn nil, errors.New(\".spec.count must >= 0\")\n\t}`)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error replacing validation logic in webhooks file %q: %w\", filename, err)\n\t}\n\t//nolint:gosec // false positive\n\tif writeFileErr := os.WriteFile(filename, []byte(str), 0o644); writeFileErr != nil {\n\t\treturn fmt.Errorf(\"error writing webhooks file %q: %w\", filename, writeFileErr)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "test/testdata/check.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\n\ncheck_directory=\"$(dirname \"$0\")/../../testdata\"\n\n# Check testdata directory first. If there are any uncommitted change, fail the test.\nif [[ $(git status ${check_directory} --porcelain) ]]; then\n  header_text \"Generate Testdata test precondition failed!\"\n  header_text \"Please commit the change under testdata directory before running the Generate Testdata test\"\n  exit 1\nfi\n\n$(dirname \"$0\")/generate.sh\n\n# Check if there are any changes to files under testdata directory.\nif [[ $(git status ${check_directory} --porcelain) ]]; then\n  git status ${check_directory} --porcelain\n  git diff ${check_directory}\n  header_text \"Generate Testdata failed!\"\n  header_text \"Please, if you have changed the scaffolding make sure you have run: make generate\"\n  exit 1\nelse\n  header_text \"Generate Testdata passed!\"\nfi\n"
  },
  {
    "path": "test/testdata/generate.sh",
    "content": "#!/usr/bin/env bash\n\n#  Copyright 2018 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\n\n# This function scaffolds test projects given a project name and flags.\n#\n# Usage:\n#\n#   scaffold_test_project <project name> <flag1> <flag2>\nfunction scaffold_test_project {\n  local project=$1\n  shift\n  local init_flags=\"$@\"\n\n  local testdata_dir=\"$(dirname \"$0\")/../../testdata\"\n  mkdir -p $testdata_dir/$project\n  rm -rf $testdata_dir/$project/*\n  pushd $testdata_dir/$project\n\n  header_text \"Generating project ${project} with flags: ${init_flags}\"\n  go mod init sigs.k8s.io/kubebuilder/testdata/$project  # our repo autodetection will traverse up to the kb module if we don't do this\n  header_text \"Initializing project ...\"\n  $kb init $init_flags --domain testproject.org --license apache2 --owner \"The Kubernetes authors\"\n\n  if [ $project == \"project-v4\" ] ; then\n    header_text 'Creating APIs ...'\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --force\n    $kb create webhook --group crew --version v1 --kind Captain --defaulting --make=false\n    $kb create webhook --group crew --version v1 --kind Captain --programmatic-validation --make=false\n\n    # Create API to test conversion from v1 to v2\n    $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false\n    $kb create api --group crew --version v2 --kind FirstMate --controller=false --resource=true --make=false\n    $kb create webhook --group crew --version v1 --kind FirstMate --conversion --make=false --spoke v2\n\n    # Create API with custom webhook paths - split to test incremental with custom paths\n    $kb create api --group crew --version v1 --kind Sailor --controller=true --resource=true --make=false\n    $kb create webhook --group crew --version v1 --kind Sailor --defaulting --defaulting-path=/custom-mutate-sailor --make=false\n    $kb create webhook --group crew --version v1 --kind Sailor --programmatic-validation --validation-path=/custom-validate-sailor --make=false\n\n    $kb create api --group crew --version v1 --kind Admiral --plural=admirales --controller=true --resource=true --namespaced=false --make=false\n    # Split to test incremental: defaulting first, then validation with custom path\n    $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting --make=false\n    $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --programmatic-validation --validation-path=/custom-validate-admiral --make=false\n    # Controller for External types\n    $kb create api --group \"cert-manager\" --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io --external-api-module=github.com/cert-manager/cert-manager@v1.20.0\n    # Webhook for External types\n    $kb create webhook --group \"cert-manager\" --version v1 --kind Issuer --defaulting --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io --external-api-module=github.com/cert-manager/cert-manager@v1.20.0\n    # Webhook for Core type\n    $kb create webhook --group core --version v1 --kind Pod --defaulting\n    # Webhook for kubernetes Core type that is part of an api group - test incremental\n    $kb create webhook --group apps --version v1 --kind Deployment --defaulting\n    $kb create webhook --group apps --version v1 --kind Deployment --programmatic-validation\n  fi\n\n  if [[ $project =~ multigroup ]]; then\n    header_text 'Creating APIs ...'\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false\n    # Test incremental webhook additions\n    $kb create webhook --group crew --version v1 --kind Captain --defaulting --make=false\n    $kb create webhook --group crew --version v1 --kind Captain --programmatic-validation --make=false\n\n    $kb create api --group ship --version v1beta1 --kind Frigate --controller=true --resource=true --make=false\n    $kb create api --group ship --version v1 --kind Destroyer --controller=true --resource=true --namespaced=false --make=false\n    $kb create webhook --group ship --version v1 --kind Destroyer --defaulting --make=false\n    $kb create api --group ship --version v2alpha1 --kind Cruiser --controller=true --resource=true --namespaced=false --make=false\n    $kb create webhook --group ship --version v2alpha1 --kind Cruiser --programmatic-validation --make=false\n\n    $kb create api --group sea-creatures --version v1beta1 --kind Kraken --controller=true --resource=true --make=false\n    $kb create api --group sea-creatures --version v1beta2 --kind Leviathan --controller=true --resource=true --make=false\n    $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false\n    # Controller for Core types\n    $kb create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false\n    $kb create api --group foo --version v1 --kind Bar --controller=true --resource=true --make=false\n    $kb create api --group fiz --version v1 --kind Bar --controller=true --resource=true --make=false\n    # Controller for External types\n    $kb create api --group \"cert-manager\" --version v1 --kind Certificate --controller=true --resource=false --make=false --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io --external-api-module=github.com/cert-manager/cert-manager@v1.20.0\n    # Webhook for External types\n    $kb create webhook --group \"cert-manager\" --version v1 --kind Issuer --defaulting --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 --external-api-domain=io --external-api-module=github.com/cert-manager/cert-manager@v1.20.0\n    # Webhook for Core type\n    $kb create webhook --group core --version v1 --kind Pod --programmatic-validation --make=false\n    # Webhook for kubernetes Core type that is part of an api group - test incremental\n    $kb create webhook --group apps --version v1 --kind Deployment --defaulting --make=false\n    $kb create webhook --group apps --version v1 --kind Deployment --programmatic-validation --make=false\n  fi\n\n  if [[ $project =~ with-plugins ]] ; then\n    header_text 'Enabling namespace-scoped deployment ...'\n    # Use edit command to test toggling namespaced mode on existing projects\n    # This test is to ensure that we don't break and we still with both flags\n    $kb edit --namespaced --force\n  fi\n\n  if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then\n    header_text 'With Optional Plugins ...'\n    header_text 'Creating APIs with deploy-image plugin ...'\n    $kb create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:1.6.26-alpine3.19 --image-container-command=\"memcached,--memory-limit=64,-o,modern,-v\" --image-container-port=\"11211\" --run-as-user=\"1001\" --plugins=\"deploy-image/v1-alpha\" --make=false\n    $kb create api --group example.com --version v1alpha1 --kind Busybox --image=busybox:1.36.1 --plugins=\"deploy-image/v1-alpha\" --make=false\n    # Create only validation webhook for Memcached\n    $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation --make=false\n    # Create API to check webhook --conversion from v1 to v2\n    $kb create api --group example.com --version v1 --kind Wordpress --controller=true --resource=true  --make=false\n    $kb create api --group example.com --version v2 --kind Wordpress --controller=false --resource=true  --make=false\n    $kb create webhook --group example.com --version v1 --kind Wordpress --conversion --make=false --spoke v2\n\n    header_text 'Editing project with Grafana plugin ...'\n    $kb edit --plugins=grafana.kubebuilder.io/v1-alpha\n  fi\n\n  make all\n  make build-installer\n\n  if [[ $project =~ with-plugins ]] ; then\n    header_text 'Editing project with Helm plugin ...'\n    $kb edit --plugins=helm.kubebuilder.io/v2-alpha\n\n    header_text 'Editing project with Auto Update plugin ...'\n    $kb edit --plugins=autoupdate.kubebuilder.io/v1-alpha --use-gh-models\n  fi\n\n  # To avoid conflicts\n  rm -f go.sum\n  go mod tidy\n  popd\n}\n\nbuild_kb\n\nscaffold_test_project project-v4 --plugins=\"go/v4\"\nscaffold_test_project project-v4-multigroup --plugins=\"go/v4\" --multigroup\nscaffold_test_project project-v4-with-plugins --plugins=\"go/v4\" --namespaced\n"
  },
  {
    "path": "test/testdata/legacy-webhook-path.sh",
    "content": "#!/usr/bin/env bash\n\n#  Copyright 2024 The Kubernetes Authors.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\n##############################\n# TODO: Remove me when go/v4 is no longer supported\n# This script i used to validate the legacy webhook path\n##############################\n\nsource \"$(dirname \"$0\")/../common.sh\"\n\n# This function scaffolds test projects given a project name and flags.\n#\n# Usage:\n#\n#   scaffold_test_project <project name> <flag1> <flag2>\nfunction scaffold_test_project {\n  local project=$1\n  shift\n  local init_flags=\"$@\"\n\n  local testdata_dir=\"$(dirname \"$0\")/../../testdata\"\n  mkdir -p $testdata_dir/$project\n  rm -rf $testdata_dir/$project/*\n  pushd $testdata_dir/$project\n\n  header_text \"Generating project ${project} with flags: ${init_flags}\"\n  go mod init sigs.k8s.io/kubebuilder/testdata/$project  # our repo autodetection will traverse up to the kb module if we don't do this\n  header_text \"Initializing project ...\"\n  $kb init $init_flags --domain testproject.org --license apache2 --owner \"The Kubernetes authors\"\n\n  if [ $project == \"legacy-project-v4\" ] ; then\n    header_text 'Creating APIs ...'\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --force\n    $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation --legacy=true\n    $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false\n    $kb create api --group crew --version v2 --kind FirstMate --controller=false --resource=true --make=false\n    $kb create webhook --group crew --version v1 --kind FirstMate --conversion --spoke v2 --legacy=true --make=false\n    $kb create api --group crew --version v1 --kind Admiral --plural=admirales --controller=true --resource=true --namespaced=false --make=false\n    $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting --legacy=true\n  fi\n\n  if [[ $project =~ multigroup ]]; then\n    header_text 'Creating APIs ...'\n    $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false\n    $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation --legacy=true\n\n    $kb create api --group ship --version v1beta1 --kind Frigate --controller=true --resource=true --make=false\n    $kb create api --group ship --version v1 --kind Frigate --controller=false --resource=true --make=false\n    $kb create webhook --group ship --version v1beta1 --kind Frigate --conversion --spoke v1 --legacy=true\n\n    $kb create api --group ship --version v1 --kind Destroyer --controller=true --resource=true --namespaced=false --make=false\n    $kb create webhook --group ship --version v1 --kind Destroyer --defaulting --legacy=true\n    $kb create api --group ship --version v2alpha1 --kind Cruiser --controller=true --resource=true --namespaced=false --make=false\n    $kb create webhook --group ship --version v2alpha1 --kind Cruiser --programmatic-validation --legacy=true\n\n    $kb create api --group sea-creatures --version v1beta1 --kind Kraken --controller=true --resource=true --make=false\n    $kb create api --group sea-creatures --version v1beta2 --kind Leviathan --controller=true --resource=true --make=false\n    $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false\n    $kb create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false\n    $kb create api --group foo --version v1 --kind Bar --controller=true --resource=true --make=false\n    $kb create api --group fiz --version v1 --kind Bar --controller=true --resource=true --make=false\n  fi\n\n  if [[ $project =~ multigroup ]] || [[ $project =~ with-plugins ]] ; then\n    header_text 'With Optional Plugins ...'\n    header_text 'Creating APIs with deploy-image plugin ...'\n    $kb create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:memcached:1.6.26-alpine3.19 --image-container-command=\"memcached,--memory-limit=64,-o,modern,-v\" --image-container-port=\"11211\" --run-as-user=\"1001\" --plugins=\"deploy-image/v1-alpha\" --make=false\n    $kb create api --group example.com --version v1alpha1 --kind Busybox --image=busybox:1.36.1 --plugins=\"deploy-image/v1-alpha\" --make=false\n    $kb create webhook --group example.com --version v1alpha1 --kind Memcached --programmatic-validation --legacy=true\n    header_text 'Editing project with Grafana plugin ...'\n    $kb edit --plugins=grafana.kubebuilder.io/v1-alpha\n  fi\n\n  make all\n  make build-installer\n  go mod tidy\n  make test\n  popd\n}\n\nbuild_kb\n\nscaffold_test_project legacy-project-v4 --plugins=\"go/v4\"\nscaffold_test_project legacy-project-v4-multigroup --plugins=\"go/v4\" --multigroup\nscaffold_test_project legacy-project-v4-with-plugins --plugins=\"go/v4\"\n"
  },
  {
    "path": "test/testdata/test.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\n\n# Executes the test of the testdata directories\nfunction test_project {\n  rm -f \"$(command -v controller-gen)\"\n  rm -f \"$(command -v kustomize)\"\n\n  header_text \"Performing tests in dir $1\"\n  pushd \"$(dirname \"$0\")/../../testdata/$1\"\n  go mod tidy\n  make test\n  popd\n}\n\nbuild_kb\n\n# Project version v4-alpha\ntest_project project-v4\ntest_project project-v4-multigroup\ntest_project project-v4-with-plugins\n"
  },
  {
    "path": "test/testdata/test_legacy.sh",
    "content": "#!/usr/bin/env bash\n# todo: remove this file when go/v2 be removed\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nsource \"$(dirname \"$0\")/../common.sh\"\n\n# Executes the test of the testdata directories\nfunction test_project {\n  rm -f \"$(command -v controller-gen)\"\n  rm -f \"$(command -v kustomize)\"\n\n  header_text \"Performing tests in dir $1\"\n  pushd \"$(dirname \"$0\")/../../testdata/$1\"\n  go mod tidy\n  go get -u golang.org/x/sys\n  make test\n  popd\n}\n\nbuild_kb\n\n# Test project v2, which relies on pre-installed envtest tools to run 'make test'.\ntools_k8s_version=\"1.19.2\"\nfetch_tools\ntest_project project-v2\ntest_project project-v3\n"
  },
  {
    "path": "test.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Source common.sh for environment setup (PATH, GOBIN, etc.)\nsource \"$(dirname \"$0\")/test/common.sh\"\n\n# Build kubebuilder binary\nbuild_kb\nfetch_tools\n\npushd . >/dev/null\n\nmake test\n\nheader_text \"All tests passed!\"\n\npopd >/dev/null\n"
  },
  {
    "path": "test_e2e.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2018 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n./test/e2e/ci.sh\n"
  },
  {
    "path": "testdata/project-v4/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "testdata/project-v4/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "testdata/project-v4/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "testdata/project-v4/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "testdata/project-v4/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "testdata/project-v4/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "testdata/project-v4/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "testdata/project-v4/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "testdata/project-v4/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "testdata/project-v4/AGENTS.md",
    "content": "# project-v4 - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "testdata/project-v4/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "testdata/project-v4/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-v4-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-v4-builder\n\t$(CONTAINER_TOOL) buildx use project-v4-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-v4-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n"
  },
  {
    "path": "testdata/project-v4/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: testproject.org\nlayout:\n- go.kubebuilder.io/v4\nprojectName: project-v4\nrepo: sigs.k8s.io/kubebuilder/testdata/project-v4\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: crew\n  kind: Captain\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: crew\n  kind: FirstMate\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\n  version: v1\n  webhooks:\n    conversion: true\n    spoke:\n    - v2\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  domain: testproject.org\n  group: crew\n  kind: FirstMate\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v2\n  version: v2\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: crew\n  kind: Sailor\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    defaultingPath: /custom-mutate-sailor\n    validation: true\n    validationPath: /custom-validate-sailor\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n  controller: true\n  domain: testproject.org\n  group: crew\n  kind: Admiral\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\n  plural: admirales\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    validationPath: /custom-validate-admiral\n    webhookVersion: v1\n- controller: true\n  domain: io\n  external: true\n  group: cert-manager\n  kind: Certificate\n  module: github.com/cert-manager/cert-manager@v1.20.0\n  path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n  version: v1\n- domain: io\n  external: true\n  group: cert-manager\n  kind: Issuer\n  module: github.com/cert-manager/cert-manager@v1.20.0\n  path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    webhookVersion: v1\n- core: true\n  group: core\n  kind: Pod\n  path: k8s.io/api/core/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    webhookVersion: v1\n- core: true\n  group: apps\n  kind: Deployment\n  path: k8s.io/api/apps/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\nversion: \"3\"\n"
  },
  {
    "path": "testdata/project-v4/README.md",
    "content": "# project-v4\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project-v4:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project-v4:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project-v4:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project-v4/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "testdata/project-v4/api/v1/admiral_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// AdmiralSpec defines the desired state of Admiral\ntype AdmiralSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Admiral. Edit admiral_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// AdmiralStatus defines the observed state of Admiral.\ntype AdmiralStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Admiral resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:path=admirales,scope=Cluster\n\n// Admiral is the Schema for the admirales API\ntype Admiral struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Admiral\n\t// +required\n\tSpec AdmiralSpec `json:\"spec\"`\n\n\t// status defines the observed state of Admiral\n\t// +optional\n\tStatus AdmiralStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// AdmiralList contains a list of Admiral\ntype AdmiralList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Admiral `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Admiral{}, &AdmiralList{})\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v1/captain_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// CaptainSpec defines the desired state of Captain\ntype CaptainSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Captain. Edit captain_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// CaptainStatus defines the observed state of Captain.\ntype CaptainStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Captain resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Captain is the Schema for the captains API\ntype Captain struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Captain\n\t// +required\n\tSpec CaptainSpec `json:\"spec\"`\n\n\t// status defines the observed state of Captain\n\t// +optional\n\tStatus CaptainStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CaptainList contains a list of Captain\ntype CaptainList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Captain `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Captain{}, &CaptainList{})\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v1/firstmate_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// Hub marks this type as a conversion hub.\nfunc (*FirstMate) Hub() {}\n"
  },
  {
    "path": "testdata/project-v4/api/v1/firstmate_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// FirstMateSpec defines the desired state of FirstMate\ntype FirstMateSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of FirstMate. Edit firstmate_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// FirstMateStatus defines the observed state of FirstMate.\ntype FirstMateStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the FirstMate resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:storageversion\n// +kubebuilder:subresource:status\n\n// FirstMate is the Schema for the firstmates API\ntype FirstMate struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of FirstMate\n\t// +required\n\tSpec FirstMateSpec `json:\"spec\"`\n\n\t// status defines the observed state of FirstMate\n\t// +optional\n\tStatus FirstMateStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// FirstMateList contains a list of FirstMate\ntype FirstMateList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []FirstMate `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&FirstMate{}, &FirstMateList{})\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the crew v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=crew.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"crew.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4/api/v1/sailor_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// SailorSpec defines the desired state of Sailor\ntype SailorSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Sailor. Edit sailor_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// SailorStatus defines the observed state of Sailor.\ntype SailorStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Sailor resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Sailor is the Schema for the sailors API\ntype Sailor struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Sailor\n\t// +required\n\tSpec SailorSpec `json:\"spec\"`\n\n\t// status defines the observed state of Sailor\n\t// +optional\n\tStatus SailorStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// SailorList contains a list of Sailor\ntype SailorList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Sailor `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Sailor{}, &SailorList{})\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Admiral) DeepCopyInto(out *Admiral) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Admiral.\nfunc (in *Admiral) DeepCopy() *Admiral {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Admiral)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Admiral) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AdmiralList) DeepCopyInto(out *AdmiralList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Admiral, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralList.\nfunc (in *AdmiralList) DeepCopy() *AdmiralList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AdmiralList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *AdmiralList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AdmiralSpec) DeepCopyInto(out *AdmiralSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralSpec.\nfunc (in *AdmiralSpec) DeepCopy() *AdmiralSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AdmiralSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *AdmiralStatus) DeepCopyInto(out *AdmiralStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralStatus.\nfunc (in *AdmiralStatus) DeepCopy() *AdmiralStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AdmiralStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Captain) DeepCopyInto(out *Captain) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Captain.\nfunc (in *Captain) DeepCopy() *Captain {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Captain)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Captain) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainList) DeepCopyInto(out *CaptainList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Captain, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainList.\nfunc (in *CaptainList) DeepCopy() *CaptainList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CaptainList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec.\nfunc (in *CaptainSpec) DeepCopy() *CaptainSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainStatus) DeepCopyInto(out *CaptainStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainStatus.\nfunc (in *CaptainStatus) DeepCopy() *CaptainStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMate) DeepCopyInto(out *FirstMate) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMate.\nfunc (in *FirstMate) DeepCopy() *FirstMate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMate)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *FirstMate) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateList) DeepCopyInto(out *FirstMateList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]FirstMate, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateList.\nfunc (in *FirstMateList) DeepCopy() *FirstMateList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *FirstMateList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec.\nfunc (in *FirstMateSpec) DeepCopy() *FirstMateSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateStatus) DeepCopyInto(out *FirstMateStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateStatus.\nfunc (in *FirstMateStatus) DeepCopy() *FirstMateStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Sailor) DeepCopyInto(out *Sailor) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sailor.\nfunc (in *Sailor) DeepCopy() *Sailor {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Sailor)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Sailor) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SailorList) DeepCopyInto(out *SailorList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Sailor, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SailorList.\nfunc (in *SailorList) DeepCopy() *SailorList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SailorList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *SailorList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SailorSpec) DeepCopyInto(out *SailorSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SailorSpec.\nfunc (in *SailorSpec) DeepCopy() *SailorSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SailorSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SailorStatus) DeepCopyInto(out *SailorStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SailorStatus.\nfunc (in *SailorStatus) DeepCopy() *SailorStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SailorStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v2/firstmate_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/conversion\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// ConvertTo converts this FirstMate (v2) to the Hub version (v1).\nfunc (src *FirstMate) ConvertTo(dstRaw conversion.Hub) error {\n\tdst := dstRaw.(*crewv1.FirstMate)\n\tlog.Printf(\"ConvertTo: Converting FirstMate from Spoke version v2 to Hub version v1;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v2 to v1\n\t// Example: Copying Spec fields\n\t// dst.Spec.Size = src.Spec.Replicas\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n\n// ConvertFrom converts the Hub version (v1) to this FirstMate (v2).\nfunc (dst *FirstMate) ConvertFrom(srcRaw conversion.Hub) error {\n\tsrc := srcRaw.(*crewv1.FirstMate)\n\tlog.Printf(\"ConvertFrom: Converting FirstMate from Hub version v1 to Spoke version v2;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v1 to v2\n\t// Example: Copying Spec fields\n\t// dst.Spec.Replicas = src.Spec.Size\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v2/firstmate_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// FirstMateSpec defines the desired state of FirstMate\ntype FirstMateSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of FirstMate. Edit firstmate_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// FirstMateStatus defines the observed state of FirstMate.\ntype FirstMateStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the FirstMate resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// FirstMate is the Schema for the firstmates API\ntype FirstMate struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of FirstMate\n\t// +required\n\tSpec FirstMateSpec `json:\"spec\"`\n\n\t// status defines the observed state of FirstMate\n\t// +optional\n\tStatus FirstMateStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// FirstMateList contains a list of FirstMate\ntype FirstMateList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []FirstMate `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&FirstMate{}, &FirstMateList{})\n}\n"
  },
  {
    "path": "testdata/project-v4/api/v2/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v2 contains API Schema definitions for the crew v2 API group.\n// +kubebuilder:object:generate=true\n// +groupName=crew.testproject.org\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"crew.testproject.org\", Version: \"v2\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4/api/v2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMate) DeepCopyInto(out *FirstMate) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMate.\nfunc (in *FirstMate) DeepCopy() *FirstMate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMate)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *FirstMate) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateList) DeepCopyInto(out *FirstMateList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]FirstMate, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateList.\nfunc (in *FirstMateList) DeepCopy() *FirstMateList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *FirstMateList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec.\nfunc (in *FirstMateSpec) DeepCopy() *FirstMateSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FirstMateStatus) DeepCopyInto(out *FirstMateStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateStatus.\nfunc (in *FirstMateStatus) DeepCopy() *FirstMateStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FirstMateStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\tcrewv2 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v2\"\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4/internal/controller\"\n\twebhookv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/internal/webhook/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(crewv1.AddToScheme(scheme))\n\tutilruntime.Must(crewv2.AddToScheme(scheme))\n\tutilruntime.Must(certmanagerv1.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n// nolint:gocyclo\nfunc main() {\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"da1d9c86.testproject.org\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := (&controller.CaptainReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Captain\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupCaptainWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Captain\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&controller.FirstMateReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"FirstMate\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupFirstMateWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"FirstMate\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&controller.SailorReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Sailor\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupSailorWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Sailor\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&controller.AdmiralReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Admiral\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupAdmiralWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Admiral\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&controller.CertificateReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Certificate\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupIssuerWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Issuer\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupPodWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Pod\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupDeploymentWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Deployment\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "testdata/project-v4/config/certmanager/certificate-metrics.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n"
  },
  {
    "path": "testdata/project-v4/config/certmanager/certificate-webhook.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4/config/certmanager/issuer.yaml",
    "content": "# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "testdata/project-v4/config/certmanager/kustomization.yaml",
    "content": "resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4/config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n"
  },
  {
    "path": "testdata/project-v4/config/crd/bases/crew.testproject.org_admirales.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: admirales.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Admiral\n    listKind: AdmiralList\n    plural: admirales\n    singular: admiral\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Admiral is the Schema for the admirales API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Admiral\n            properties:\n              foo:\n                description: foo is an example field of Admiral. Edit admiral_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Admiral\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Admiral resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4/config/crd/bases/crew.testproject.org_captains.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: captains.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Captain\n    listKind: CaptainList\n    plural: captains\n    singular: captain\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Captain is the Schema for the captains API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Captain\n            properties:\n              foo:\n                description: foo is an example field of Captain. Edit captain_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Captain\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Captain resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4/config/crd/bases/crew.testproject.org_firstmates.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: firstmates.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: FirstMate\n    listKind: FirstMateList\n    plural: firstmates\n    singular: firstmate\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: FirstMate is the Schema for the firstmates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of FirstMate\n            properties:\n              foo:\n                description: foo is an example field of FirstMate. Edit firstmate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of FirstMate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the FirstMate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: FirstMate is the Schema for the firstmates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of FirstMate\n            properties:\n              foo:\n                description: foo is an example field of FirstMate. Edit firstmate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of FirstMate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the FirstMate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4/config/crd/bases/crew.testproject.org_sailors.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: sailors.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Sailor\n    listKind: SailorList\n    plural: sailors\n    singular: sailor\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Sailor is the Schema for the sailors API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Sailor\n            properties:\n              foo:\n                description: foo is an example field of Sailor. Edit sailor_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Sailor\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Sailor resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/crew.testproject.org_captains.yaml\n- bases/crew.testproject.org_firstmates.yaml\n- bases/crew.testproject.org_sailors.yaml\n- bases/crew.testproject.org_admirales.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n- path: patches/webhook_in_firstmates.yaml\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "testdata/project-v4/config/crd/patches/webhook_in_firstmates.yaml",
    "content": "# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: firstmates.crew.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n"
  },
  {
    "path": "testdata/project-v4/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "testdata/project-v4/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-v4-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-v4-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\nreplacements:\n# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n - source:\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.namespace # Namespace of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert # This name should match the one in certificate.yaml\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: firstmates.crew.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: firstmates.crew.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "testdata/project-v4/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "testdata/project-v4/config/default/manager_webhook_patch.yaml",
    "content": "# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n"
  },
  {
    "path": "testdata/project-v4/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "testdata/project-v4/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project-v4\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "testdata/project-v4/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4/config/network-policy/allow-webhook-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-webhook-traffic.yaml\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "testdata/project-v4/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor\n"
  },
  {
    "path": "testdata/project-v4/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4\n"
  },
  {
    "path": "testdata/project-v4/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/admiral_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over crew.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: admiral-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/admiral_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the crew.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: admiral-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/admiral_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to crew.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: admiral-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/captain_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over crew.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: captain-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/captain_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the crew.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: captain-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/captain_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to crew.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: captain-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/firstmate_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over crew.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: firstmate-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/firstmate_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the crew.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: firstmate-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/firstmate_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to crew.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: firstmate-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project-v4 itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- admiral_admin_role.yaml\n- admiral_editor_role.yaml\n- admiral_viewer_role.yaml\n- sailor_admin_role.yaml\n- sailor_editor_role.yaml\n- sailor_viewer_role.yaml\n- firstmate_admin_role.yaml\n- firstmate_editor_role.yaml\n- firstmate_viewer_role.yaml\n- captain_admin_role.yaml\n- captain_editor_role.yaml\n- captain_viewer_role.yaml\n\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  - captains\n  - firstmates\n  - sailors\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/finalizers\n  - captains/finalizers\n  - firstmates/finalizers\n  - sailors/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  - captains/status\n  - firstmates/status\n  - sailors/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/sailor_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over crew.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: sailor-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/sailor_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the crew.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: sailor-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/sailor_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4 itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to crew.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: sailor-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4/config/samples/crew_v1_admiral.yaml",
    "content": "apiVersion: crew.testproject.org/v1\nkind: Admiral\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: admiral-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4/config/samples/crew_v1_captain.yaml",
    "content": "apiVersion: crew.testproject.org/v1\nkind: Captain\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: captain-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4/config/samples/crew_v1_firstmate.yaml",
    "content": "apiVersion: crew.testproject.org/v1\nkind: FirstMate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: firstmate-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4/config/samples/crew_v1_sailor.yaml",
    "content": "apiVersion: crew.testproject.org/v1\nkind: Sailor\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: sailor-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4/config/samples/crew_v2_firstmate.yaml",
    "content": "apiVersion: crew.testproject.org/v2\nkind: FirstMate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: firstmate-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- crew_v1_captain.yaml\n- crew_v1_firstmate.yaml\n- crew_v2_firstmate.yaml\n- crew_v1_sailor.yaml\n- crew_v1_admiral.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "testdata/project-v4/config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n"
  },
  {
    "path": "testdata/project-v4/config/webhook/manifests.yaml",
    "content": "---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-crew-testproject-org-v1-admiral\n  failurePolicy: Fail\n  name: madmiral-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - admirales\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: mcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-apps-v1-deployment\n  failurePolicy: Fail\n  name: mdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-cert-manager-io-v1-issuer\n  failurePolicy: Fail\n  name: missuer-v1.kb.io\n  rules:\n  - apiGroups:\n    - cert-manager.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - issuers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate--v1-pod\n  failurePolicy: Fail\n  name: mpod-v1.kb.io\n  rules:\n  - apiGroups:\n    - \"\"\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - pods\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /custom-mutate-sailor\n  failurePolicy: Fail\n  name: msailor-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - sailors\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /custom-validate-admiral\n  failurePolicy: Fail\n  name: vadmiral-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - admirales\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: vcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-apps-v1-deployment\n  failurePolicy: Fail\n  name: vdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /custom-validate-sailor\n  failurePolicy: Fail\n  name: vsailor-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - sailors\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4/config/webhook/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4\n"
  },
  {
    "path": "testdata/project-v4/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n    control-plane: controller-manager\n  name: project-v4-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: admirales.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Admiral\n    listKind: AdmiralList\n    plural: admirales\n    singular: admiral\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Admiral is the Schema for the admirales API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Admiral\n            properties:\n              foo:\n                description: foo is an example field of Admiral. Edit admiral_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Admiral\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Admiral resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: captains.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Captain\n    listKind: CaptainList\n    plural: captains\n    singular: captain\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Captain is the Schema for the captains API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Captain\n            properties:\n              foo:\n                description: foo is an example field of Captain. Edit captain_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Captain\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Captain resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: firstmates.crew.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: project-v4-webhook-service\n          namespace: project-v4-system\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: crew.testproject.org\n  names:\n    kind: FirstMate\n    listKind: FirstMateList\n    plural: firstmates\n    singular: firstmate\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: FirstMate is the Schema for the firstmates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of FirstMate\n            properties:\n              foo:\n                description: foo is an example field of FirstMate. Edit firstmate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of FirstMate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the FirstMate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: FirstMate is the Schema for the firstmates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of FirstMate\n            properties:\n              foo:\n                description: foo is an example field of FirstMate. Edit firstmate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of FirstMate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the FirstMate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: sailors.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Sailor\n    listKind: SailorList\n    plural: sailors\n    singular: sailor\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Sailor is the Schema for the sailors API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Sailor\n            properties:\n              foo:\n                description: foo is an example field of Sailor. Edit sailor_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Sailor\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Sailor resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-controller-manager\n  namespace: project-v4-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-leader-election-role\n  namespace: project-v4-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-admiral-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-admiral-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-admiral-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-captain-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-captain-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-captain-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-firstmate-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-firstmate-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-firstmate-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - firstmates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-manager-role\nrules:\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales\n  - captains\n  - firstmates\n  - sailors\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/finalizers\n  - captains/finalizers\n  - firstmates/finalizers\n  - sailors/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - admirales/status\n  - captains/status\n  - firstmates/status\n  - sailors/status\n  verbs:\n  - get\n  - patch\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-sailor-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-sailor-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-sailor-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - sailors/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-leader-election-rolebinding\n  namespace: project-v4-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-v4-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-controller-manager\n  namespace: project-v4-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-v4-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-controller-manager\n  namespace: project-v4-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-v4-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-v4-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-controller-manager\n  namespace: project-v4-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n    control-plane: controller-manager\n  name: project-v4-controller-manager-metrics-service\n  namespace: project-v4-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project-v4\n    control-plane: controller-manager\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-webhook-service\n  namespace: project-v4-system\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  selector:\n    app.kubernetes.io/name: project-v4\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n    control-plane: controller-manager\n  name: project-v4-controller-manager\n  namespace: project-v4-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project-v4\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project-v4\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        command:\n        - /manager\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-v4-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes:\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-metrics-certs\n  namespace: project-v4-system\nspec:\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-selfsigned-issuer\n  secretName: metrics-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-serving-cert\n  namespace: project-v4-system\nspec:\n  dnsNames:\n  - project-v4-webhook-service.project-v4-system.svc\n  - project-v4-webhook-service.project-v4-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-selfsigned-issuer\n  secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4\n  name: project-v4-selfsigned-issuer\n  namespace: project-v4-system\nspec:\n  selfSigned: {}\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert\n  name: project-v4-mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /mutate-crew-testproject-org-v1-admiral\n  failurePolicy: Fail\n  name: madmiral-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - admirales\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /mutate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: mcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /mutate-apps-v1-deployment\n  failurePolicy: Fail\n  name: mdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /mutate-cert-manager-io-v1-issuer\n  failurePolicy: Fail\n  name: missuer-v1.kb.io\n  rules:\n  - apiGroups:\n    - cert-manager.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - issuers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /mutate--v1-pod\n  failurePolicy: Fail\n  name: mpod-v1.kb.io\n  rules:\n  - apiGroups:\n    - \"\"\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - pods\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /custom-mutate-sailor\n  failurePolicy: Fail\n  name: msailor-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - sailors\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-system/project-v4-serving-cert\n  name: project-v4-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /custom-validate-admiral\n  failurePolicy: Fail\n  name: vadmiral-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - admirales\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /validate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: vcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /validate-apps-v1-deployment\n  failurePolicy: Fail\n  name: vdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-webhook-service\n      namespace: project-v4-system\n      path: /custom-validate-sailor\n  failurePolicy: Fail\n  name: vsailor-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - sailors\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4/go.mod",
    "content": "module sigs.k8s.io/kubebuilder/testdata/project-v4\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/cert-manager/cert-manager v1.20.0\n\tgithub.com/onsi/ginkgo/v2 v2.28.0\n\tgithub.com/onsi/gomega v1.39.1\n\tk8s.io/api v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/client-go v0.35.2\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.25.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.13.0 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.22.4 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.4 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.25.4 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.1 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.9.1 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.17.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.1 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.40.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.7.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.1 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.5.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect\n\tgoogle.golang.org/grpc v1.79.1 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.2 // indirect\n\tk8s.io/apiserver v0.35.2 // indirect\n\tk8s.io/component-base v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.140.0 // indirect\n\tk8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect\n\tk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect\n\tsigs.k8s.io/gateway-api v1.5.0 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "testdata/project-v4/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "testdata/project-v4/internal/controller/admiral_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// AdmiralReconciler reconciles a Admiral object\ntype AdmiralReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=admirales,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=admirales/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=admirales/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Admiral object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *AdmiralReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&crewv1.Admiral{}).\n\t\tNamed(\"admiral\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/admiral_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\nvar _ = Describe(\"Admiral Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tadmiral := &crewv1.Admiral{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Admiral\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, admiral)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &crewv1.Admiral{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &crewv1.Admiral{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Admiral\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &AdmiralReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/captain_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// CaptainReconciler reconciles a Captain object\ntype CaptainReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Captain object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&crewv1.Captain{}).\n\t\tNamed(\"captain\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/captain_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\nvar _ = Describe(\"Captain Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tcaptain := &crewv1.Captain{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Captain\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, captain)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &crewv1.Captain{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &crewv1.Captain{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Captain\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &CaptainReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/certificate_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// CertificateReconciler reconciles a Certificate object\ntype CertificateReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Certificate object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CertificateReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&certmanagerv1.Certificate{}).\n\t\tNamed(\"certificate\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/certificate_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n)\n\nvar _ = Describe(\"Certificate Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/firstmate_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// FirstMateReconciler reconciles a FirstMate object\ntype FirstMateReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the FirstMate object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *FirstMateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&crewv1.FirstMate{}).\n\t\tNamed(\"firstmate\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/firstmate_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\nvar _ = Describe(\"FirstMate Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tfirstmate := &crewv1.FirstMate{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind FirstMate\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, firstmate)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &crewv1.FirstMate{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &crewv1.FirstMate{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance FirstMate\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &FirstMateReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/sailor_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// SailorReconciler reconciles a Sailor object\ntype SailorReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=sailors,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=sailors/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=sailors/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Sailor object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *SailorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *SailorReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&crewv1.Sailor{}).\n\t\tNamed(\"sailor\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/sailor_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\nvar _ = Describe(\"Sailor Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tsailor := &crewv1.Sailor{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Sailor\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, sailor)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &crewv1.Sailor{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &crewv1.Sailor{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Sailor\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &SailorReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/controller/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = crewv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = certmanagerv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/admiral_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar admirallog = logf.Log.WithName(\"admiral-resource\")\n\n// SetupAdmiralWebhookWithManager registers the webhook for Admiral in the manager.\nfunc SetupAdmiralWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &crewv1.Admiral{}).\n\t\tWithDefaulter(&AdmiralCustomDefaulter{}).\n\t\tWithValidator(&AdmiralCustomValidator{}).\n\t\tWithValidatorCustomPath(\"/custom-validate-admiral\").\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-admiral,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=admirales,verbs=create;update,versions=v1,name=madmiral-v1.kb.io,admissionReviewVersions=v1\n\n// AdmiralCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Admiral when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype AdmiralCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Admiral.\nfunc (d *AdmiralCustomDefaulter) Default(_ context.Context, obj *crewv1.Admiral) error {\n\tadmirallog.Info(\"Defaulting for Admiral\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/custom-validate-admiral,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=admirales,verbs=create;update,versions=v1,name=vadmiral-v1.kb.io,admissionReviewVersions=v1\n\n// AdmiralCustomValidator struct is responsible for validating the Admiral resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype AdmiralCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Admiral.\nfunc (v *AdmiralCustomValidator) ValidateCreate(_ context.Context, obj *crewv1.Admiral) (admission.Warnings, error) {\n\tadmirallog.Info(\"Validation for Admiral upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Admiral.\nfunc (v *AdmiralCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *crewv1.Admiral) (admission.Warnings, error) {\n\tadmirallog.Info(\"Validation for Admiral upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Admiral.\nfunc (v *AdmiralCustomValidator) ValidateDelete(_ context.Context, obj *crewv1.Admiral) (admission.Warnings, error) {\n\tadmirallog.Info(\"Validation for Admiral upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/admiral_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Admiral Webhook\", func() {\n\tvar (\n\t\tobj       *crewv1.Admiral\n\t\toldObj    *crewv1.Admiral\n\t\tdefaulter AdmiralCustomDefaulter\n\t\tvalidator AdmiralCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &crewv1.Admiral{}\n\t\toldObj = &crewv1.Admiral{}\n\t\tdefaulter = AdmiralCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = AdmiralCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Admiral under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Admiral under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/captain_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar captainlog = logf.Log.WithName(\"captain-resource\")\n\n// SetupCaptainWebhookWithManager registers the webhook for Captain in the manager.\nfunc SetupCaptainWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &crewv1.Captain{}).\n\t\tWithDefaulter(&CaptainCustomDefaulter{}).\n\t\tWithValidator(&CaptainCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-captain,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=mcaptain-v1.kb.io,admissionReviewVersions=v1\n\n// CaptainCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Captain when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype CaptainCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain.\nfunc (d *CaptainCustomDefaulter) Default(_ context.Context, obj *crewv1.Captain) error {\n\tcaptainlog.Info(\"Defaulting for Captain\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-crew-testproject-org-v1-captain,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=vcaptain-v1.kb.io,admissionReviewVersions=v1\n\n// CaptainCustomValidator struct is responsible for validating the Captain resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CaptainCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateCreate(_ context.Context, obj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateDelete(_ context.Context, obj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/captain_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Captain Webhook\", func() {\n\tvar (\n\t\tobj       *crewv1.Captain\n\t\toldObj    *crewv1.Captain\n\t\tdefaulter CaptainCustomDefaulter\n\t\tvalidator CaptainCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &crewv1.Captain{}\n\t\toldObj = &crewv1.Captain{}\n\t\tdefaulter = CaptainCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = CaptainCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Captain under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Captain under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/deployment_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar deploymentlog = logf.Log.WithName(\"deployment-resource\")\n\n// SetupDeploymentWebhookWithManager registers the webhook for Deployment in the manager.\nfunc SetupDeploymentWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &appsv1.Deployment{}).\n\t\tWithDefaulter(&DeploymentCustomDefaulter{}).\n\t\tWithValidator(&DeploymentCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-apps-v1-deployment,mutating=true,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=mdeployment-v1.kb.io,admissionReviewVersions=v1\n\n// DeploymentCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Deployment when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype DeploymentCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Deployment.\nfunc (d *DeploymentCustomDefaulter) Default(_ context.Context, obj *appsv1.Deployment) error {\n\tdeploymentlog.Info(\"Defaulting for Deployment\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-apps-v1-deployment,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=vdeployment-v1.kb.io,admissionReviewVersions=v1\n\n// DeploymentCustomValidator struct is responsible for validating the Deployment resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype DeploymentCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateCreate(_ context.Context, obj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateDelete(_ context.Context, obj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/deployment_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Deployment Webhook\", func() {\n\tvar (\n\t\tobj       *appsv1.Deployment\n\t\toldObj    *appsv1.Deployment\n\t\tdefaulter DeploymentCustomDefaulter\n\t\tvalidator DeploymentCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &appsv1.Deployment{}\n\t\toldObj = &appsv1.Deployment{}\n\t\tdefaulter = DeploymentCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = DeploymentCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Deployment under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Deployment under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/firstmate_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar firstmatelog = logf.Log.WithName(\"firstmate-resource\")\n\n// SetupFirstMateWebhookWithManager registers the webhook for FirstMate in the manager.\nfunc SetupFirstMateWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &crewv1.FirstMate{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/firstmate_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"FirstMate Webhook\", func() {\n\tvar (\n\t\tobj    *crewv1.FirstMate\n\t\toldObj *crewv1.FirstMate\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &crewv1.FirstMate{}\n\t\toldObj = &crewv1.FirstMate{}\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating FirstMate under Conversion Webhook\", func() {\n\t\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t\t// Example:\n\t\t// It(\"Should convert the object correctly\", func() {\n\t\t//     convertedObj := &crewv1.FirstMate{}\n\t\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t\t//     Expect(convertedObj).ToNot(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/issuer_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar issuerlog = logf.Log.WithName(\"issuer-resource\")\n\n// SetupIssuerWebhookWithManager registers the webhook for Issuer in the manager.\nfunc SetupIssuerWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &certmanagerv1.Issuer{}).\n\t\tWithDefaulter(&IssuerCustomDefaulter{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-cert-manager-io-v1-issuer,mutating=true,failurePolicy=fail,sideEffects=None,groups=cert-manager.io,resources=issuers,verbs=create;update,versions=v1,name=missuer-v1.kb.io,admissionReviewVersions=v1\n\n// IssuerCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Issuer when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype IssuerCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Issuer.\nfunc (d *IssuerCustomDefaulter) Default(_ context.Context, obj *certmanagerv1.Issuer) error {\n\tissuerlog.Info(\"Defaulting for Issuer\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/issuer_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Issuer Webhook\", func() {\n\tvar (\n\t\tobj       *certmanagerv1.Issuer\n\t\toldObj    *certmanagerv1.Issuer\n\t\tdefaulter IssuerCustomDefaulter\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &certmanagerv1.Issuer{}\n\t\toldObj = &certmanagerv1.Issuer{}\n\t\tdefaulter = IssuerCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Issuer under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/pod_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar podlog = logf.Log.WithName(\"pod-resource\")\n\n// SetupPodWebhookWithManager registers the webhook for Pod in the manager.\nfunc SetupPodWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &corev1.Pod{}).\n\t\tWithDefaulter(&PodCustomDefaulter{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate--v1-pod,mutating=true,failurePolicy=fail,sideEffects=None,groups=\"\",resources=pods,verbs=create;update,versions=v1,name=mpod-v1.kb.io,admissionReviewVersions=v1\n\n// PodCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Pod when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype PodCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Pod.\nfunc (d *PodCustomDefaulter) Default(_ context.Context, obj *corev1.Pod) error {\n\tpodlog.Info(\"Defaulting for Pod\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/pod_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Pod Webhook\", func() {\n\tvar (\n\t\tobj       *corev1.Pod\n\t\toldObj    *corev1.Pod\n\t\tdefaulter PodCustomDefaulter\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &corev1.Pod{}\n\t\toldObj = &corev1.Pod{}\n\t\tdefaulter = PodCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Pod under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/sailor_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar sailorlog = logf.Log.WithName(\"sailor-resource\")\n\n// SetupSailorWebhookWithManager registers the webhook for Sailor in the manager.\nfunc SetupSailorWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &crewv1.Sailor{}).\n\t\tWithDefaulter(&SailorCustomDefaulter{}).\n\t\tWithDefaulterCustomPath(\"/custom-mutate-sailor\").\n\t\tWithValidator(&SailorCustomValidator{}).\n\t\tWithValidatorCustomPath(\"/custom-validate-sailor\").\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/custom-mutate-sailor,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=sailors,verbs=create;update,versions=v1,name=msailor-v1.kb.io,admissionReviewVersions=v1\n\n// SailorCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Sailor when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype SailorCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Sailor.\nfunc (d *SailorCustomDefaulter) Default(_ context.Context, obj *crewv1.Sailor) error {\n\tsailorlog.Info(\"Defaulting for Sailor\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/custom-validate-sailor,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=sailors,verbs=create;update,versions=v1,name=vsailor-v1.kb.io,admissionReviewVersions=v1\n\n// SailorCustomValidator struct is responsible for validating the Sailor resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype SailorCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Sailor.\nfunc (v *SailorCustomValidator) ValidateCreate(_ context.Context, obj *crewv1.Sailor) (admission.Warnings, error) {\n\tsailorlog.Info(\"Validation for Sailor upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Sailor.\nfunc (v *SailorCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *crewv1.Sailor) (admission.Warnings, error) {\n\tsailorlog.Info(\"Validation for Sailor upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Sailor.\nfunc (v *SailorCustomValidator) ValidateDelete(_ context.Context, obj *crewv1.Sailor) (admission.Warnings, error) {\n\tsailorlog.Info(\"Validation for Sailor upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/sailor_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Sailor Webhook\", func() {\n\tvar (\n\t\tobj       *crewv1.Sailor\n\t\toldObj    *crewv1.Sailor\n\t\tdefaulter SailorCustomDefaulter\n\t\tvalidator SailorCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &crewv1.Sailor{}\n\t\toldObj = &crewv1.Sailor{}\n\t\tdefaulter = SailorCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = SailorCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Sailor under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Sailor under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4/internal/webhook/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = crewv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCaptainWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupFirstMateWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupSailorWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupAdmiralWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupIssuerWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupPodWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupDeploymentWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project-v4:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project-v4 e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n})\n\nvar _ = AfterSuite(func() {\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "testdata/project-v4/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-v4-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-v4-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-v4-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-v4-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-v4-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\t\t\"-l\", \"kubernetes.io/service-name=project-v4-webhook-service\",\n\t\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t\t}\n\t\t\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the mutating webhook server is ready\")\n\t\t\tverifyMutatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"MutatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Mutating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyMutatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should provisioned cert-manager\", func() {\n\t\t\tBy(\"validating that cert-manager has the certificate Secret\")\n\t\t\tverifyCertManager := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t\t\t_, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t\tEventually(verifyCertManager).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for mutating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for mutating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tmwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for validating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for validating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for FirstMate conversion webhook\", func() {\n\t\t\tBy(\"checking CA injection for FirstMate conversion webhook\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"customresourcedefinitions.apiextensions.k8s.io\",\n\t\t\t\t\t\"firstmates.crew.testproject.org\",\n\t\t\t\t\t\"-o\", \"go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "testdata/project-v4/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "testdata/project-v4-multigroup/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "testdata/project-v4-multigroup/AGENTS.md",
    "content": "# project-v4-multigroup - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "testdata/project-v4-multigroup/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "testdata/project-v4-multigroup/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-v4-multigroup-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-v4-multigroup-builder\n\t$(CONTAINER_TOOL) buildx use project-v4-multigroup-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-v4-multigroup-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n"
  },
  {
    "path": "testdata/project-v4-multigroup/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: testproject.org\nlayout:\n- go.kubebuilder.io/v4\nmultigroup: true\nplugins:\n  deploy-image.go.kubebuilder.io/v1-alpha:\n    resources:\n    - domain: testproject.org\n      group: example.com\n      kind: Memcached\n      options:\n        containerCommand: memcached,--memory-limit=64,-o,modern,-v\n        containerPort: \"11211\"\n        image: memcached:1.6.26-alpine3.19\n        runAsUser: \"1001\"\n      version: v1alpha1\n    - domain: testproject.org\n      group: example.com\n      kind: Busybox\n      options:\n        image: busybox:1.36.1\n      version: v1alpha1\n  grafana.kubebuilder.io/v1-alpha: {}\nprojectName: project-v4-multigroup\nrepo: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: crew\n  kind: Captain\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: ship\n  kind: Frigate\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1\n  version: v1beta1\n- api:\n    crdVersion: v1\n  controller: true\n  domain: testproject.org\n  group: ship\n  kind: Destroyer\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n  controller: true\n  domain: testproject.org\n  group: ship\n  kind: Cruiser\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\n  version: v2alpha1\n  webhooks:\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: sea-creatures\n  kind: Kraken\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta1\n  version: v1beta1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: sea-creatures\n  kind: Leviathan\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta2\n  version: v1beta2\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: foo.policy\n  kind: HealthCheckPolicy\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1\n  version: v1\n- controller: true\n  core: true\n  group: apps\n  kind: Deployment\n  path: k8s.io/api/apps/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: foo\n  kind: Bar\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1\n  version: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: fiz\n  kind: Bar\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1\n  version: v1\n- controller: true\n  domain: io\n  external: true\n  group: cert-manager\n  kind: Certificate\n  module: github.com/cert-manager/cert-manager@v1.20.0\n  path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n  version: v1\n- domain: io\n  external: true\n  group: cert-manager\n  kind: Issuer\n  module: github.com/cert-manager/cert-manager@v1.20.0\n  path: github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\n  version: v1\n  webhooks:\n    defaulting: true\n    webhookVersion: v1\n- core: true\n  group: core\n  kind: Pod\n  path: k8s.io/api/core/v1\n  version: v1\n  webhooks:\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Memcached\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\n  version: v1alpha1\n  webhooks:\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Busybox\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\n  version: v1alpha1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Wordpress\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\n  version: v1\n  webhooks:\n    conversion: true\n    spoke:\n    - v2\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  domain: testproject.org\n  group: example.com\n  kind: Wordpress\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v2\n  version: v2\nversion: \"3\"\n"
  },
  {
    "path": "testdata/project-v4-multigroup/README.md",
    "content": "# project-v4-multigroup\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project-v4-multigroup:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project-v4-multigroup:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project-v4-multigroup:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project-v4-multigroup/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/crew/v1/captain_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// CaptainSpec defines the desired state of Captain\ntype CaptainSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Captain. Edit captain_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// CaptainStatus defines the observed state of Captain.\ntype CaptainStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Captain resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Captain is the Schema for the captains API\ntype Captain struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Captain\n\t// +required\n\tSpec CaptainSpec `json:\"spec\"`\n\n\t// status defines the observed state of Captain\n\t// +optional\n\tStatus CaptainStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CaptainList contains a list of Captain\ntype CaptainList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Captain `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Captain{}, &CaptainList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/crew/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the crew v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=crew.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"crew.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/crew/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Captain) DeepCopyInto(out *Captain) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Captain.\nfunc (in *Captain) DeepCopy() *Captain {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Captain)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Captain) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainList) DeepCopyInto(out *CaptainList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Captain, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainList.\nfunc (in *CaptainList) DeepCopy() *CaptainList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CaptainList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec.\nfunc (in *CaptainSpec) DeepCopy() *CaptainSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CaptainStatus) DeepCopyInto(out *CaptainStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainStatus.\nfunc (in *CaptainStatus) DeepCopy() *CaptainStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CaptainStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the example.com v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1/wordpress_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// Hub marks this type as a conversion hub.\nfunc (*Wordpress) Hub() {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1/wordpress_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// WordpressSpec defines the desired state of Wordpress\ntype WordpressSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Wordpress. Edit wordpress_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// WordpressStatus defines the observed state of Wordpress.\ntype WordpressStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Wordpress resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:storageversion\n// +kubebuilder:subresource:status\n\n// Wordpress is the Schema for the wordpresses API\ntype Wordpress struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Wordpress\n\t// +required\n\tSpec WordpressSpec `json:\"spec\"`\n\n\t// status defines the observed state of Wordpress\n\t// +optional\n\tStatus WordpressStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// WordpressList contains a list of Wordpress\ntype WordpressList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Wordpress `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Wordpress{}, &WordpressList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Wordpress) DeepCopyInto(out *Wordpress) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wordpress.\nfunc (in *Wordpress) DeepCopy() *Wordpress {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Wordpress)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Wordpress) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressList) DeepCopyInto(out *WordpressList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Wordpress, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressList.\nfunc (in *WordpressList) DeepCopy() *WordpressList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *WordpressList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec.\nfunc (in *WordpressSpec) DeepCopy() *WordpressSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressStatus) DeepCopyInto(out *WordpressStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressStatus.\nfunc (in *WordpressStatus) DeepCopy() *WordpressStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1alpha1/busybox_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// BusyboxSpec defines the desired state of Busybox\ntype BusyboxSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of Busybox instances\n\t// +kubebuilder:default=1\n\t// +kubebuilder:validation:Minimum=0\n\t// +optional\n\tSize *int32 `json:\"size,omitempty\"`\n}\n\n// BusyboxStatus defines the observed state of Busybox\ntype BusyboxStatus struct {\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Busybox resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Busybox is the Schema for the busyboxes API\ntype Busybox struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Busybox\n\t// +required\n\tSpec BusyboxSpec `json:\"spec\"`\n\n\t// status defines the observed state of Busybox\n\t// +optional\n\tStatus BusyboxStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// BusyboxList contains a list of Busybox\ntype BusyboxList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Busybox `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Busybox{}, &BusyboxList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1alpha1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha1 contains API Schema definitions for the example.com v1alpha1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v1alpha1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1alpha1/memcached_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// MemcachedSpec defines the desired state of Memcached\ntype MemcachedSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of Memcached instances\n\t// +kubebuilder:default=1\n\t// +kubebuilder:validation:Minimum=0\n\t// +optional\n\tSize *int32 `json:\"size,omitempty\"`\n\n\t// containerPort defines the port that will be used to init the container with the image\n\t// +required\n\tContainerPort int32 `json:\"containerPort\"`\n}\n\n// MemcachedStatus defines the observed state of Memcached\ntype MemcachedStatus struct {\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Memcached resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Memcached is the Schema for the memcacheds API\ntype Memcached struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Memcached\n\t// +required\n\tSpec MemcachedSpec `json:\"spec\"`\n\n\t// status defines the observed state of Memcached\n\t// +optional\n\tStatus MemcachedStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// MemcachedList contains a list of Memcached\ntype MemcachedList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Memcached `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Memcached{}, &MemcachedList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Busybox) DeepCopyInto(out *Busybox) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Busybox.\nfunc (in *Busybox) DeepCopy() *Busybox {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Busybox)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Busybox) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxList) DeepCopyInto(out *BusyboxList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Busybox, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxList.\nfunc (in *BusyboxList) DeepCopy() *BusyboxList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *BusyboxList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) {\n\t*out = *in\n\tif in.Size != nil {\n\t\tin, out := &in.Size, &out.Size\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec.\nfunc (in *BusyboxSpec) DeepCopy() *BusyboxSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxStatus) DeepCopyInto(out *BusyboxStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxStatus.\nfunc (in *BusyboxStatus) DeepCopy() *BusyboxStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Memcached) DeepCopyInto(out *Memcached) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached.\nfunc (in *Memcached) DeepCopy() *Memcached {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Memcached)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Memcached) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedList) DeepCopyInto(out *MemcachedList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Memcached, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList.\nfunc (in *MemcachedList) DeepCopy() *MemcachedList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MemcachedList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) {\n\t*out = *in\n\tif in.Size != nil {\n\t\tin, out := &in.Size, &out.Size\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec.\nfunc (in *MemcachedSpec) DeepCopy() *MemcachedSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus.\nfunc (in *MemcachedStatus) DeepCopy() *MemcachedStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v2/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v2 contains API Schema definitions for the example.com v2 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v2\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v2/wordpress_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/conversion\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n)\n\n// ConvertTo converts this Wordpress (v2) to the Hub version (v1).\nfunc (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error {\n\tdst := dstRaw.(*examplecomv1.Wordpress)\n\tlog.Printf(\"ConvertTo: Converting Wordpress from Spoke version v2 to Hub version v1;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v2 to v1\n\t// Example: Copying Spec fields\n\t// dst.Spec.Size = src.Spec.Replicas\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n\n// ConvertFrom converts the Hub version (v1) to this Wordpress (v2).\nfunc (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error {\n\tsrc := srcRaw.(*examplecomv1.Wordpress)\n\tlog.Printf(\"ConvertFrom: Converting Wordpress from Hub version v1 to Spoke version v2;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v1 to v2\n\t// Example: Copying Spec fields\n\t// dst.Spec.Replicas = src.Spec.Size\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v2/wordpress_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// WordpressSpec defines the desired state of Wordpress\ntype WordpressSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Wordpress. Edit wordpress_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// WordpressStatus defines the observed state of Wordpress.\ntype WordpressStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Wordpress resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Wordpress is the Schema for the wordpresses API\ntype Wordpress struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Wordpress\n\t// +required\n\tSpec WordpressSpec `json:\"spec\"`\n\n\t// status defines the observed state of Wordpress\n\t// +optional\n\tStatus WordpressStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// WordpressList contains a list of Wordpress\ntype WordpressList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Wordpress `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Wordpress{}, &WordpressList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/example.com/v2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Wordpress) DeepCopyInto(out *Wordpress) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wordpress.\nfunc (in *Wordpress) DeepCopy() *Wordpress {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Wordpress)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Wordpress) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressList) DeepCopyInto(out *WordpressList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Wordpress, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressList.\nfunc (in *WordpressList) DeepCopy() *WordpressList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *WordpressList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec.\nfunc (in *WordpressSpec) DeepCopy() *WordpressSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressStatus) DeepCopyInto(out *WordpressStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressStatus.\nfunc (in *WordpressStatus) DeepCopy() *WordpressStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/fiz/v1/bar_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// BarSpec defines the desired state of Bar\ntype BarSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Bar. Edit bar_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// BarStatus defines the observed state of Bar.\ntype BarStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Bar resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Bar is the Schema for the bars API\ntype Bar struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Bar\n\t// +required\n\tSpec BarSpec `json:\"spec\"`\n\n\t// status defines the observed state of Bar\n\t// +optional\n\tStatus BarStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// BarList contains a list of Bar\ntype BarList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Bar `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Bar{}, &BarList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/fiz/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the fiz v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=fiz.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"fiz.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/fiz/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Bar) DeepCopyInto(out *Bar) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bar.\nfunc (in *Bar) DeepCopy() *Bar {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Bar)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Bar) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarList) DeepCopyInto(out *BarList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Bar, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarList.\nfunc (in *BarList) DeepCopy() *BarList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *BarList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarSpec) DeepCopyInto(out *BarSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarSpec.\nfunc (in *BarSpec) DeepCopy() *BarSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarStatus) DeepCopyInto(out *BarStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarStatus.\nfunc (in *BarStatus) DeepCopy() *BarStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo/v1/bar_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// BarSpec defines the desired state of Bar\ntype BarSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Bar. Edit bar_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// BarStatus defines the observed state of Bar.\ntype BarStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Bar resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Bar is the Schema for the bars API\ntype Bar struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Bar\n\t// +required\n\tSpec BarSpec `json:\"spec\"`\n\n\t// status defines the observed state of Bar\n\t// +optional\n\tStatus BarStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// BarList contains a list of Bar\ntype BarList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Bar `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Bar{}, &BarList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the foo v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=foo.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"foo.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Bar) DeepCopyInto(out *Bar) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bar.\nfunc (in *Bar) DeepCopy() *Bar {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Bar)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Bar) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarList) DeepCopyInto(out *BarList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Bar, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarList.\nfunc (in *BarList) DeepCopy() *BarList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *BarList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarSpec) DeepCopyInto(out *BarSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarSpec.\nfunc (in *BarSpec) DeepCopy() *BarSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BarStatus) DeepCopyInto(out *BarStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BarStatus.\nfunc (in *BarStatus) DeepCopy() *BarStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BarStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo.policy/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the foo.policy v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=foo.policy.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"foo.policy.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo.policy/v1/healthcheckpolicy_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// HealthCheckPolicySpec defines the desired state of HealthCheckPolicy\ntype HealthCheckPolicySpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// HealthCheckPolicyStatus defines the observed state of HealthCheckPolicy.\ntype HealthCheckPolicyStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the HealthCheckPolicy resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// HealthCheckPolicy is the Schema for the healthcheckpolicies API\ntype HealthCheckPolicy struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of HealthCheckPolicy\n\t// +required\n\tSpec HealthCheckPolicySpec `json:\"spec\"`\n\n\t// status defines the observed state of HealthCheckPolicy\n\t// +optional\n\tStatus HealthCheckPolicyStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// HealthCheckPolicyList contains a list of HealthCheckPolicy\ntype HealthCheckPolicyList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []HealthCheckPolicy `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&HealthCheckPolicy{}, &HealthCheckPolicyList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/foo.policy/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HealthCheckPolicy) DeepCopyInto(out *HealthCheckPolicy) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckPolicy.\nfunc (in *HealthCheckPolicy) DeepCopy() *HealthCheckPolicy {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HealthCheckPolicy)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HealthCheckPolicy) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HealthCheckPolicyList) DeepCopyInto(out *HealthCheckPolicyList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]HealthCheckPolicy, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckPolicyList.\nfunc (in *HealthCheckPolicyList) DeepCopy() *HealthCheckPolicyList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HealthCheckPolicyList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *HealthCheckPolicyList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HealthCheckPolicySpec) DeepCopyInto(out *HealthCheckPolicySpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckPolicySpec.\nfunc (in *HealthCheckPolicySpec) DeepCopy() *HealthCheckPolicySpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HealthCheckPolicySpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *HealthCheckPolicyStatus) DeepCopyInto(out *HealthCheckPolicyStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthCheckPolicyStatus.\nfunc (in *HealthCheckPolicyStatus) DeepCopy() *HealthCheckPolicyStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(HealthCheckPolicyStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1beta1 contains API Schema definitions for the sea-creatures v1beta1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=sea-creatures.testproject.org\npackage v1beta1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"sea-creatures.testproject.org\", Version: \"v1beta1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta1/kraken_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// KrakenSpec defines the desired state of Kraken\ntype KrakenSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Kraken. Edit kraken_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// KrakenStatus defines the observed state of Kraken.\ntype KrakenStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Kraken resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Kraken is the Schema for the krakens API\ntype Kraken struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Kraken\n\t// +required\n\tSpec KrakenSpec `json:\"spec\"`\n\n\t// status defines the observed state of Kraken\n\t// +optional\n\tStatus KrakenStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// KrakenList contains a list of Kraken\ntype KrakenList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Kraken `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Kraken{}, &KrakenList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Kraken) DeepCopyInto(out *Kraken) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kraken.\nfunc (in *Kraken) DeepCopy() *Kraken {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Kraken)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Kraken) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *KrakenList) DeepCopyInto(out *KrakenList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Kraken, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KrakenList.\nfunc (in *KrakenList) DeepCopy() *KrakenList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KrakenList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *KrakenList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *KrakenSpec) DeepCopyInto(out *KrakenSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KrakenSpec.\nfunc (in *KrakenSpec) DeepCopy() *KrakenSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KrakenSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *KrakenStatus) DeepCopyInto(out *KrakenStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KrakenStatus.\nfunc (in *KrakenStatus) DeepCopy() *KrakenStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KrakenStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta2/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1beta2 contains API Schema definitions for the sea-creatures v1beta2 API group.\n// +kubebuilder:object:generate=true\n// +groupName=sea-creatures.testproject.org\npackage v1beta2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"sea-creatures.testproject.org\", Version: \"v1beta2\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta2/leviathan_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// LeviathanSpec defines the desired state of Leviathan\ntype LeviathanSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Leviathan. Edit leviathan_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// LeviathanStatus defines the observed state of Leviathan.\ntype LeviathanStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Leviathan resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Leviathan is the Schema for the leviathans API\ntype Leviathan struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Leviathan\n\t// +required\n\tSpec LeviathanSpec `json:\"spec\"`\n\n\t// status defines the observed state of Leviathan\n\t// +optional\n\tStatus LeviathanStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// LeviathanList contains a list of Leviathan\ntype LeviathanList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Leviathan `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Leviathan{}, &LeviathanList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/sea-creatures/v1beta2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Leviathan) DeepCopyInto(out *Leviathan) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Leviathan.\nfunc (in *Leviathan) DeepCopy() *Leviathan {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Leviathan)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Leviathan) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LeviathanList) DeepCopyInto(out *LeviathanList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Leviathan, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeviathanList.\nfunc (in *LeviathanList) DeepCopy() *LeviathanList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LeviathanList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *LeviathanList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LeviathanSpec) DeepCopyInto(out *LeviathanSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeviathanSpec.\nfunc (in *LeviathanSpec) DeepCopy() *LeviathanSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LeviathanSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LeviathanStatus) DeepCopyInto(out *LeviathanStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeviathanStatus.\nfunc (in *LeviathanStatus) DeepCopy() *LeviathanStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LeviathanStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1/destroyer_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// DestroyerSpec defines the desired state of Destroyer\ntype DestroyerSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Destroyer. Edit destroyer_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// DestroyerStatus defines the observed state of Destroyer.\ntype DestroyerStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Destroyer resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Cluster\n\n// Destroyer is the Schema for the destroyers API\ntype Destroyer struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Destroyer\n\t// +required\n\tSpec DestroyerSpec `json:\"spec\"`\n\n\t// status defines the observed state of Destroyer\n\t// +optional\n\tStatus DestroyerStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// DestroyerList contains a list of Destroyer\ntype DestroyerList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Destroyer `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Destroyer{}, &DestroyerList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the ship v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=ship.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"ship.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Destroyer) DeepCopyInto(out *Destroyer) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Destroyer.\nfunc (in *Destroyer) DeepCopy() *Destroyer {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Destroyer)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Destroyer) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *DestroyerList) DeepCopyInto(out *DestroyerList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Destroyer, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerList.\nfunc (in *DestroyerList) DeepCopy() *DestroyerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(DestroyerList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *DestroyerList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *DestroyerSpec) DeepCopyInto(out *DestroyerSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerSpec.\nfunc (in *DestroyerSpec) DeepCopy() *DestroyerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(DestroyerSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *DestroyerStatus) DeepCopyInto(out *DestroyerStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DestroyerStatus.\nfunc (in *DestroyerStatus) DeepCopy() *DestroyerStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(DestroyerStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1beta1/frigate_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// FrigateSpec defines the desired state of Frigate\ntype FrigateSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Frigate. Edit frigate_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// FrigateStatus defines the observed state of Frigate.\ntype FrigateStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Frigate resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Frigate is the Schema for the frigates API\ntype Frigate struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Frigate\n\t// +required\n\tSpec FrigateSpec `json:\"spec\"`\n\n\t// status defines the observed state of Frigate\n\t// +optional\n\tStatus FrigateStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// FrigateList contains a list of Frigate\ntype FrigateList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Frigate `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Frigate{}, &FrigateList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1beta1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1beta1 contains API Schema definitions for the ship v1beta1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=ship.testproject.org\npackage v1beta1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"ship.testproject.org\", Version: \"v1beta1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Frigate) DeepCopyInto(out *Frigate) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Frigate.\nfunc (in *Frigate) DeepCopy() *Frigate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Frigate)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Frigate) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FrigateList) DeepCopyInto(out *FrigateList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Frigate, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrigateList.\nfunc (in *FrigateList) DeepCopy() *FrigateList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FrigateList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *FrigateList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FrigateSpec) DeepCopyInto(out *FrigateSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrigateSpec.\nfunc (in *FrigateSpec) DeepCopy() *FrigateSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FrigateSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FrigateStatus) DeepCopyInto(out *FrigateStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrigateStatus.\nfunc (in *FrigateStatus) DeepCopy() *FrigateStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FrigateStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v2alpha1/cruiser_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// CruiserSpec defines the desired state of Cruiser\ntype CruiserSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Cruiser. Edit cruiser_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// CruiserStatus defines the observed state of Cruiser.\ntype CruiserStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Cruiser resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Cluster\n\n// Cruiser is the Schema for the cruisers API\ntype Cruiser struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Cruiser\n\t// +required\n\tSpec CruiserSpec `json:\"spec\"`\n\n\t// status defines the observed state of Cruiser\n\t// +optional\n\tStatus CruiserStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// CruiserList contains a list of Cruiser\ntype CruiserList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Cruiser `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Cruiser{}, &CruiserList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v2alpha1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v2alpha1 contains API Schema definitions for the ship v2alpha1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=ship.testproject.org\npackage v2alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"ship.testproject.org\", Version: \"v2alpha1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/api/ship/v2alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v2alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Cruiser) DeepCopyInto(out *Cruiser) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cruiser.\nfunc (in *Cruiser) DeepCopy() *Cruiser {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Cruiser)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Cruiser) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CruiserList) DeepCopyInto(out *CruiserList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Cruiser, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserList.\nfunc (in *CruiserList) DeepCopy() *CruiserList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CruiserList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *CruiserList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CruiserSpec) DeepCopyInto(out *CruiserSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserSpec.\nfunc (in *CruiserSpec) DeepCopy() *CruiserSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CruiserSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *CruiserStatus) DeepCopyInto(out *CruiserStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CruiserStatus.\nfunc (in *CruiserStatus) DeepCopy() *CruiserStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CruiserStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"os\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n\texamplecomv2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v2\"\n\tfizv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1\"\n\tfoopolicyv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1\"\n\tfoov1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1\"\n\tseacreaturesv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta1\"\n\tseacreaturesv1beta2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta2\"\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n\tshipv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1\"\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n\tappscontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/apps\"\n\tcertmanagercontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/cert-manager\"\n\tcrewcontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/crew\"\n\texamplecomcontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/example.com\"\n\tfizcontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/fiz\"\n\tfoocontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo\"\n\tfoopolicycontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/foo.policy\"\n\tseacreaturescontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/sea-creatures\"\n\tshipcontroller \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/controller/ship\"\n\twebhookappsv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/apps/v1\"\n\twebhookcertmanagerv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/cert-manager/v1\"\n\twebhookcorev1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/core/v1\"\n\twebhookcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/crew/v1\"\n\twebhookexamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1\"\n\twebhookexamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1\"\n\twebhookshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v1\"\n\twebhookshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(crewv1.AddToScheme(scheme))\n\tutilruntime.Must(shipv1beta1.AddToScheme(scheme))\n\tutilruntime.Must(shipv1.AddToScheme(scheme))\n\tutilruntime.Must(shipv2alpha1.AddToScheme(scheme))\n\tutilruntime.Must(seacreaturesv1beta1.AddToScheme(scheme))\n\tutilruntime.Must(seacreaturesv1beta2.AddToScheme(scheme))\n\tutilruntime.Must(foopolicyv1.AddToScheme(scheme))\n\tutilruntime.Must(foov1.AddToScheme(scheme))\n\tutilruntime.Must(fizv1.AddToScheme(scheme))\n\tutilruntime.Must(certmanagerv1.AddToScheme(scheme))\n\tutilruntime.Must(examplecomv1alpha1.AddToScheme(scheme))\n\tutilruntime.Must(examplecomv1.AddToScheme(scheme))\n\tutilruntime.Must(examplecomv2.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n// nolint:gocyclo\nfunc main() {\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"3e9f67a9.testproject.org\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := (&crewcontroller.CaptainReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Captain\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookcrewv1.SetupCaptainWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Captain\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&shipcontroller.FrigateReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Frigate\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&shipcontroller.DestroyerReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Destroyer\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookshipv1.SetupDestroyerWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Destroyer\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&shipcontroller.CruiserReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Cruiser\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookshipv2alpha1.SetupCruiserWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Cruiser\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&seacreaturescontroller.KrakenReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Kraken\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&seacreaturescontroller.LeviathanReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Leviathan\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&foopolicycontroller.HealthCheckPolicyReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"HealthCheckPolicy\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&appscontroller.DeploymentReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Deployment\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&foocontroller.BarReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Bar\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&fizcontroller.BarReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Bar\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&certmanagercontroller.CertificateReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Certificate\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookcertmanagerv1.SetupIssuerWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Issuer\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookcorev1.SetupPodWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Pod\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookappsv1.SetupDeploymentWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Deployment\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&examplecomcontroller.MemcachedReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\tRecorder: mgr.GetEventRecorder(\"memcached-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Memcached\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&examplecomcontroller.BusyboxReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\tRecorder: mgr.GetEventRecorder(\"busybox-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Busybox\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookexamplecomv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Memcached\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&examplecomcontroller.WordpressReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Wordpress\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookexamplecomv1.SetupWordpressWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Wordpress\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/certmanager/certificate-metrics.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/certmanager/certificate-webhook.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/certmanager/issuer.yaml",
    "content": "# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/certmanager/kustomization.yaml",
    "content": "resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/crew.testproject.org_captains.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: captains.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Captain\n    listKind: CaptainList\n    plural: captains\n    singular: captain\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Captain is the Schema for the captains API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Captain\n            properties:\n              foo:\n                description: foo is an example field of Captain. Edit captain_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Captain\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Captain resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_busyboxes.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: busyboxes.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Busybox\n    listKind: BusyboxList\n    plural: busyboxes\n    singular: busybox\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Busybox is the Schema for the busyboxes API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Busybox\n            properties:\n              size:\n                default: 1\n                description: size defines the number of Busybox instances\n                format: int32\n                minimum: 0\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Busybox\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Busybox resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_memcacheds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              containerPort:\n                description: containerPort defines the port that will be used to init\n                  the container with the image\n                format: int32\n                type: integer\n              size:\n                default: 1\n                description: size defines the number of Memcached instances\n                format: int32\n                minimum: 0\n                type: integer\n            required:\n            - containerPort\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/example.com.testproject.org_wordpresses.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: wordpresses.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Wordpress\n    listKind: WordpressList\n    plural: wordpresses\n    singular: wordpress\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/fiz.testproject.org_bars.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: bars.fiz.testproject.org\nspec:\n  group: fiz.testproject.org\n  names:\n    kind: Bar\n    listKind: BarList\n    plural: bars\n    singular: bar\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Bar is the Schema for the bars API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Bar\n            properties:\n              foo:\n                description: foo is an example field of Bar. Edit bar_types.go to\n                  remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Bar\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Bar resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/foo.policy.testproject.org_healthcheckpolicies.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: healthcheckpolicies.foo.policy.testproject.org\nspec:\n  group: foo.policy.testproject.org\n  names:\n    kind: HealthCheckPolicy\n    listKind: HealthCheckPolicyList\n    plural: healthcheckpolicies\n    singular: healthcheckpolicy\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: HealthCheckPolicy is the Schema for the healthcheckpolicies API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of HealthCheckPolicy\n            properties:\n              foo:\n                description: foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of HealthCheckPolicy\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the HealthCheckPolicy resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/foo.testproject.org_bars.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: bars.foo.testproject.org\nspec:\n  group: foo.testproject.org\n  names:\n    kind: Bar\n    listKind: BarList\n    plural: bars\n    singular: bar\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Bar is the Schema for the bars API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Bar\n            properties:\n              foo:\n                description: foo is an example field of Bar. Edit bar_types.go to\n                  remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Bar\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Bar resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_krakens.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: krakens.sea-creatures.testproject.org\nspec:\n  group: sea-creatures.testproject.org\n  names:\n    kind: Kraken\n    listKind: KrakenList\n    plural: krakens\n    singular: kraken\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: Kraken is the Schema for the krakens API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Kraken\n            properties:\n              foo:\n                description: foo is an example field of Kraken. Edit kraken_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Kraken\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Kraken resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/sea-creatures.testproject.org_leviathans.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: leviathans.sea-creatures.testproject.org\nspec:\n  group: sea-creatures.testproject.org\n  names:\n    kind: Leviathan\n    listKind: LeviathanList\n    plural: leviathans\n    singular: leviathan\n  scope: Namespaced\n  versions:\n  - name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: Leviathan is the Schema for the leviathans API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Leviathan\n            properties:\n              foo:\n                description: foo is an example field of Leviathan. Edit leviathan_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Leviathan\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Leviathan resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_cruisers.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cruisers.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Cruiser\n    listKind: CruiserList\n    plural: cruisers\n    singular: cruiser\n  scope: Cluster\n  versions:\n  - name: v2alpha1\n    schema:\n      openAPIV3Schema:\n        description: Cruiser is the Schema for the cruisers API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Cruiser\n            properties:\n              foo:\n                description: foo is an example field of Cruiser. Edit cruiser_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Cruiser\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Cruiser resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_destroyers.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: destroyers.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Destroyer\n    listKind: DestroyerList\n    plural: destroyers\n    singular: destroyer\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Destroyer is the Schema for the destroyers API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Destroyer\n            properties:\n              foo:\n                description: foo is an example field of Destroyer. Edit destroyer_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Destroyer\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Destroyer resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/bases/ship.testproject.org_frigates.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: frigates.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Frigate\n    listKind: FrigateList\n    plural: frigates\n    singular: frigate\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: Frigate is the Schema for the frigates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Frigate\n            properties:\n              foo:\n                description: foo is an example field of Frigate. Edit frigate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Frigate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Frigate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/crew.testproject.org_captains.yaml\n- bases/ship.testproject.org_frigates.yaml\n- bases/ship.testproject.org_destroyers.yaml\n- bases/ship.testproject.org_cruisers.yaml\n- bases/sea-creatures.testproject.org_krakens.yaml\n- bases/sea-creatures.testproject.org_leviathans.yaml\n- bases/foo.policy.testproject.org_healthcheckpolicies.yaml\n- bases/foo.testproject.org_bars.yaml\n- bases/fiz.testproject.org_bars.yaml\n- bases/example.com.testproject.org_memcacheds.yaml\n- bases/example.com.testproject.org_busyboxes.yaml\n- bases/example.com.testproject.org_wordpresses.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n- path: patches/webhook_in_example.com_wordpresses.yaml\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/crd/patches/webhook_in_example.com_wordpresses.yaml",
    "content": "# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: wordpresses.example.com.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-v4-multigroup-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-v4-multigroup-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\nreplacements:\n# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n - source:\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.namespace # Namespace of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert # This name should match the one in certificate.yaml\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: MutatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: wordpresses.example.com.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: wordpresses.example.com.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/default/manager_webhook_patch.yaml",
    "content": "# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-multigroup\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project-v4-multigroup\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        env:\n        - name: BUSYBOX_IMAGE\n          value: busybox:1.36.1\n        - name: MEMCACHED_IMAGE\n          value: memcached:1.6.26-alpine3.19\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-multigroup\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/network-policy/allow-webhook-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-multigroup\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-webhook-traffic.yaml\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-multigroup\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/crew_captain_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over crew.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: crew-captain-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/crew_captain_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the crew.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: crew-captain-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/crew_captain_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to crew.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: crew-captain-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_busybox_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-busybox-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_busybox_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-busybox-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_busybox_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-busybox-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_memcached_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-memcached-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_memcached_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-memcached-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_memcached_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-memcached-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_wordpress_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-wordpress-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_wordpress_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-wordpress-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/example.com_wordpress_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: example.com-wordpress-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/fiz_bar_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over fiz.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: fiz-bar-admin-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - '*'\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/fiz_bar_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the fiz.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: fiz-bar-editor-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/fiz_bar_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to fiz.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: fiz-bar-viewer-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo.policy_healthcheckpolicy_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over foo.policy.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo.policy-healthcheckpolicy-admin-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - '*'\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo.policy_healthcheckpolicy_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the foo.policy.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo.policy-healthcheckpolicy-editor-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo.policy_healthcheckpolicy_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to foo.policy.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo.policy-healthcheckpolicy-viewer-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo_bar_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over foo.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo-bar-admin-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - '*'\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo_bar_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the foo.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo-bar-editor-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/foo_bar_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to foo.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: foo-bar-viewer-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project-v4-multigroup itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- example.com_wordpress_admin_role.yaml\n- example.com_wordpress_editor_role.yaml\n- example.com_wordpress_viewer_role.yaml\n- example.com_busybox_admin_role.yaml\n- example.com_busybox_editor_role.yaml\n- example.com_busybox_viewer_role.yaml\n- example.com_memcached_admin_role.yaml\n- example.com_memcached_editor_role.yaml\n- example.com_memcached_viewer_role.yaml\n- fiz_bar_admin_role.yaml\n- fiz_bar_editor_role.yaml\n- fiz_bar_viewer_role.yaml\n- foo_bar_admin_role.yaml\n- foo_bar_editor_role.yaml\n- foo_bar_viewer_role.yaml\n- foo.policy_healthcheckpolicy_admin_role.yaml\n- foo.policy_healthcheckpolicy_editor_role.yaml\n- foo.policy_healthcheckpolicy_viewer_role.yaml\n- sea-creatures_leviathan_admin_role.yaml\n- sea-creatures_leviathan_editor_role.yaml\n- sea-creatures_leviathan_viewer_role.yaml\n- sea-creatures_kraken_admin_role.yaml\n- sea-creatures_kraken_editor_role.yaml\n- sea-creatures_kraken_viewer_role.yaml\n- ship_cruiser_admin_role.yaml\n- ship_cruiser_editor_role.yaml\n- ship_cruiser_viewer_role.yaml\n- ship_destroyer_admin_role.yaml\n- ship_destroyer_editor_role.yaml\n- ship_destroyer_viewer_role.yaml\n- ship_frigate_admin_role.yaml\n- ship_frigate_editor_role.yaml\n- ship_frigate_viewer_role.yaml\n- crew_captain_admin_role.yaml\n- crew_captain_editor_role.yaml\n- crew_captain_viewer_role.yaml\n\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: manager-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - apps\n  resources:\n  - deployments/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  - memcacheds\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/finalizers\n  - memcacheds/finalizers\n  - wordpresses/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  - memcacheds/status\n  - wordpresses/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  - leviathans\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/finalizers\n  - leviathans/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  - leviathans/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  - destroyers\n  - frigates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/finalizers\n  - destroyers/finalizers\n  - frigates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  - destroyers/status\n  - frigates/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_kraken_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over sea-creatures.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-kraken-admin-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - '*'\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_kraken_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the sea-creatures.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-kraken-editor-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_kraken_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to sea-creatures.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-kraken-viewer-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_leviathan_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over sea-creatures.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-leviathan-admin-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - '*'\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_leviathan_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the sea-creatures.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-leviathan-editor-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/sea-creatures_leviathan_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to sea-creatures.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: sea-creatures-leviathan-viewer-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_cruiser_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over ship.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-cruiser-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_cruiser_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the ship.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-cruiser-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_cruiser_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to ship.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-cruiser-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_destroyer_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over ship.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-destroyer-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_destroyer_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the ship.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-destroyer-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_destroyer_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to ship.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-destroyer-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_frigate_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over ship.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-frigate-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_frigate_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the ship.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-frigate-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/rbac/ship_frigate_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-multigroup itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to ship.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: ship-frigate-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/crew_v1_captain.yaml",
    "content": "apiVersion: crew.testproject.org/v1\nkind: Captain\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: captain-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/example.com_v1_wordpress.yaml",
    "content": "apiVersion: example.com.testproject.org/v1\nkind: Wordpress\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/example.com_v1alpha1_busybox.yaml",
    "content": "apiVersion: example.com.testproject.org/v1alpha1\nkind: Busybox\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: busybox-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/example.com_v1alpha1_memcached.yaml",
    "content": "apiVersion: example.com.testproject.org/v1alpha1\nkind: Memcached\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n\n  # TODO(user): edit the following value to ensure the container has the right port to be initialized\n  containerPort: 11211\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/example.com_v2_wordpress.yaml",
    "content": "apiVersion: example.com.testproject.org/v2\nkind: Wordpress\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/fiz_v1_bar.yaml",
    "content": "apiVersion: fiz.testproject.org/v1\nkind: Bar\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: bar-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/foo.policy_v1_healthcheckpolicy.yaml",
    "content": "apiVersion: foo.policy.testproject.org/v1\nkind: HealthCheckPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: healthcheckpolicy-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/foo_v1_bar.yaml",
    "content": "apiVersion: foo.testproject.org/v1\nkind: Bar\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: bar-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- crew_v1_captain.yaml\n- ship_v1beta1_frigate.yaml\n- ship_v1_destroyer.yaml\n- ship_v2alpha1_cruiser.yaml\n- sea-creatures_v1beta1_kraken.yaml\n- sea-creatures_v1beta2_leviathan.yaml\n- foo.policy_v1_healthcheckpolicy.yaml\n- foo_v1_bar.yaml\n- fiz_v1_bar.yaml\n- example.com_v1alpha1_memcached.yaml\n- example.com_v1alpha1_busybox.yaml\n- example.com_v1_wordpress.yaml\n- example.com_v2_wordpress.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/sea-creatures_v1beta1_kraken.yaml",
    "content": "apiVersion: sea-creatures.testproject.org/v1beta1\nkind: Kraken\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: kraken-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/sea-creatures_v1beta2_leviathan.yaml",
    "content": "apiVersion: sea-creatures.testproject.org/v1beta2\nkind: Leviathan\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: leviathan-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/ship_v1_destroyer.yaml",
    "content": "apiVersion: ship.testproject.org/v1\nkind: Destroyer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: destroyer-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/ship_v1beta1_frigate.yaml",
    "content": "apiVersion: ship.testproject.org/v1beta1\nkind: Frigate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: frigate-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/samples/ship_v2alpha1_cruiser.yaml",
    "content": "apiVersion: ship.testproject.org/v2alpha1\nkind: Cruiser\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: cruiser-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/webhook/manifests.yaml",
    "content": "---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: mcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-apps-v1-deployment\n  failurePolicy: Fail\n  name: mdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-ship-testproject-org-v1-destroyer\n  failurePolicy: Fail\n  name: mdestroyer-v1.kb.io\n  rules:\n  - apiGroups:\n    - ship.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - destroyers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /mutate-cert-manager-io-v1-issuer\n  failurePolicy: Fail\n  name: missuer-v1.kb.io\n  rules:\n  - apiGroups:\n    - cert-manager.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - issuers\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: vcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-ship-testproject-org-v2alpha1-cruiser\n  failurePolicy: Fail\n  name: vcruiser-v2alpha1.kb.io\n  rules:\n  - apiGroups:\n    - ship.testproject.org\n    apiVersions:\n    - v2alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cruisers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-apps-v1-deployment\n  failurePolicy: Fail\n  name: vdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-example-com-testproject-org-v1alpha1-memcached\n  failurePolicy: Fail\n  name: vmemcached-v1alpha1.kb.io\n  rules:\n  - apiGroups:\n    - example.com.testproject.org\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - memcacheds\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate--v1-pod\n  failurePolicy: Fail\n  name: vpod-v1.kb.io\n  rules:\n  - apiGroups:\n    - \"\"\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - pods\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4-multigroup/config/webhook/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-multigroup\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-multigroup\n"
  },
  {
    "path": "testdata/project-v4-multigroup/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n    control-plane: controller-manager\n  name: project-v4-multigroup-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: bars.fiz.testproject.org\nspec:\n  group: fiz.testproject.org\n  names:\n    kind: Bar\n    listKind: BarList\n    plural: bars\n    singular: bar\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Bar is the Schema for the bars API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Bar\n            properties:\n              foo:\n                description: foo is an example field of Bar. Edit bar_types.go to\n                  remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Bar\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Bar resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: bars.foo.testproject.org\nspec:\n  group: foo.testproject.org\n  names:\n    kind: Bar\n    listKind: BarList\n    plural: bars\n    singular: bar\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Bar is the Schema for the bars API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Bar\n            properties:\n              foo:\n                description: foo is an example field of Bar. Edit bar_types.go to\n                  remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Bar\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Bar resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: busyboxes.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Busybox\n    listKind: BusyboxList\n    plural: busyboxes\n    singular: busybox\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Busybox is the Schema for the busyboxes API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Busybox\n            properties:\n              size:\n                default: 1\n                description: size defines the number of Busybox instances\n                format: int32\n                minimum: 0\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Busybox\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Busybox resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: captains.crew.testproject.org\nspec:\n  group: crew.testproject.org\n  names:\n    kind: Captain\n    listKind: CaptainList\n    plural: captains\n    singular: captain\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Captain is the Schema for the captains API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Captain\n            properties:\n              foo:\n                description: foo is an example field of Captain. Edit captain_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Captain\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Captain resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: cruisers.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Cruiser\n    listKind: CruiserList\n    plural: cruisers\n    singular: cruiser\n  scope: Cluster\n  versions:\n  - name: v2alpha1\n    schema:\n      openAPIV3Schema:\n        description: Cruiser is the Schema for the cruisers API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Cruiser\n            properties:\n              foo:\n                description: foo is an example field of Cruiser. Edit cruiser_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Cruiser\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Cruiser resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: destroyers.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Destroyer\n    listKind: DestroyerList\n    plural: destroyers\n    singular: destroyer\n  scope: Cluster\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Destroyer is the Schema for the destroyers API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Destroyer\n            properties:\n              foo:\n                description: foo is an example field of Destroyer. Edit destroyer_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Destroyer\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Destroyer resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: frigates.ship.testproject.org\nspec:\n  group: ship.testproject.org\n  names:\n    kind: Frigate\n    listKind: FrigateList\n    plural: frigates\n    singular: frigate\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: Frigate is the Schema for the frigates API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Frigate\n            properties:\n              foo:\n                description: foo is an example field of Frigate. Edit frigate_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Frigate\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Frigate resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: healthcheckpolicies.foo.policy.testproject.org\nspec:\n  group: foo.policy.testproject.org\n  names:\n    kind: HealthCheckPolicy\n    listKind: HealthCheckPolicyList\n    plural: healthcheckpolicies\n    singular: healthcheckpolicy\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: HealthCheckPolicy is the Schema for the healthcheckpolicies API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of HealthCheckPolicy\n            properties:\n              foo:\n                description: foo is an example field of HealthCheckPolicy. Edit healthcheckpolicy_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of HealthCheckPolicy\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the HealthCheckPolicy resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: krakens.sea-creatures.testproject.org\nspec:\n  group: sea-creatures.testproject.org\n  names:\n    kind: Kraken\n    listKind: KrakenList\n    plural: krakens\n    singular: kraken\n  scope: Namespaced\n  versions:\n  - name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: Kraken is the Schema for the krakens API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Kraken\n            properties:\n              foo:\n                description: foo is an example field of Kraken. Edit kraken_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Kraken\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Kraken resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: leviathans.sea-creatures.testproject.org\nspec:\n  group: sea-creatures.testproject.org\n  names:\n    kind: Leviathan\n    listKind: LeviathanList\n    plural: leviathans\n    singular: leviathan\n  scope: Namespaced\n  versions:\n  - name: v1beta2\n    schema:\n      openAPIV3Schema:\n        description: Leviathan is the Schema for the leviathans API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Leviathan\n            properties:\n              foo:\n                description: foo is an example field of Leviathan. Edit leviathan_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Leviathan\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Leviathan resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              containerPort:\n                description: containerPort defines the port that will be used to init\n                  the container with the image\n                format: int32\n                type: integer\n              size:\n                default: 1\n                description: size defines the number of Memcached instances\n                format: int32\n                minimum: 0\n                type: integer\n            required:\n            - containerPort\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: wordpresses.example.com.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: project-v4-multigroup-webhook-service\n          namespace: project-v4-multigroup-system\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: example.com.testproject.org\n  names:\n    kind: Wordpress\n    listKind: WordpressList\n    plural: wordpresses\n    singular: wordpress\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-controller-manager\n  namespace: project-v4-multigroup-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-leader-election-role\n  namespace: project-v4-multigroup-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-crew-captain-admin-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - '*'\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-crew-captain-editor-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-crew-captain-viewer-role\nrules:\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-busybox-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-busybox-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-busybox-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-memcached-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-memcached-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-memcached-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-wordpress-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-wordpress-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-example.com-wordpress-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-fiz-bar-admin-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - '*'\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-fiz-bar-editor-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-fiz-bar-viewer-role\nrules:\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo-bar-admin-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - '*'\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo-bar-editor-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo-bar-viewer-role\nrules:\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo.policy-healthcheckpolicy-admin-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - '*'\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo.policy-healthcheckpolicy-editor-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-foo.policy-healthcheckpolicy-viewer-role\nrules:\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-multigroup-manager-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - apps\n  resources:\n  - deployments/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - cert-manager.io\n  resources:\n  - certificates/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - crew.testproject.org\n  resources:\n  - captains/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  - memcacheds\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/finalizers\n  - memcacheds/finalizers\n  - wordpresses/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  - memcacheds/status\n  - wordpresses/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - fiz.testproject.org\n  - foo.testproject.org\n  resources:\n  - bars/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - foo.policy.testproject.org\n  resources:\n  - healthcheckpolicies/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  - leviathans\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/finalizers\n  - leviathans/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  - leviathans/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  - destroyers\n  - frigates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/finalizers\n  - destroyers/finalizers\n  - frigates/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  - destroyers/status\n  - frigates/status\n  verbs:\n  - get\n  - patch\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-multigroup-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-multigroup-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-kraken-admin-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - '*'\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-kraken-editor-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-kraken-viewer-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - krakens/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-leviathan-admin-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - '*'\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-leviathan-editor-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-sea-creatures-leviathan-viewer-role\nrules:\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - sea-creatures.testproject.org\n  resources:\n  - leviathans/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-cruiser-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-cruiser-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-cruiser-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - cruisers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-destroyer-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-destroyer-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-destroyer-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - destroyers/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-frigate-admin-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - '*'\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-frigate-editor-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-ship-frigate-viewer-role\nrules:\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - ship.testproject.org\n  resources:\n  - frigates/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-leader-election-rolebinding\n  namespace: project-v4-multigroup-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-v4-multigroup-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-multigroup-controller-manager\n  namespace: project-v4-multigroup-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-v4-multigroup-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-multigroup-controller-manager\n  namespace: project-v4-multigroup-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-v4-multigroup-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-v4-multigroup-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-multigroup-controller-manager\n  namespace: project-v4-multigroup-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n    control-plane: controller-manager\n  name: project-v4-multigroup-controller-manager-metrics-service\n  namespace: project-v4-multigroup-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project-v4-multigroup\n    control-plane: controller-manager\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-webhook-service\n  namespace: project-v4-multigroup-system\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  selector:\n    app.kubernetes.io/name: project-v4-multigroup\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n    control-plane: controller-manager\n  name: project-v4-multigroup-controller-manager\n  namespace: project-v4-multigroup-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project-v4-multigroup\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project-v4-multigroup\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        command:\n        - /manager\n        env:\n        - name: BUSYBOX_IMAGE\n          value: busybox:1.36.1\n        - name: MEMCACHED_IMAGE\n          value: memcached:1.6.26-alpine3.19\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-v4-multigroup-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes:\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-metrics-certs\n  namespace: project-v4-multigroup-system\nspec:\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-multigroup-selfsigned-issuer\n  secretName: metrics-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-serving-cert\n  namespace: project-v4-multigroup-system\nspec:\n  dnsNames:\n  - project-v4-multigroup-webhook-service.project-v4-multigroup-system.svc\n  - project-v4-multigroup-webhook-service.project-v4-multigroup-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-multigroup-selfsigned-issuer\n  secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-multigroup\n  name: project-v4-multigroup-selfsigned-issuer\n  namespace: project-v4-multigroup-system\nspec:\n  selfSigned: {}\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert\n  name: project-v4-multigroup-mutating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /mutate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: mcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /mutate-apps-v1-deployment\n  failurePolicy: Fail\n  name: mdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /mutate-ship-testproject-org-v1-destroyer\n  failurePolicy: Fail\n  name: mdestroyer-v1.kb.io\n  rules:\n  - apiGroups:\n    - ship.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - destroyers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /mutate-cert-manager-io-v1-issuer\n  failurePolicy: Fail\n  name: missuer-v1.kb.io\n  rules:\n  - apiGroups:\n    - cert-manager.io\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - issuers\n  sideEffects: None\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-multigroup-system/project-v4-multigroup-serving-cert\n  name: project-v4-multigroup-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /validate-crew-testproject-org-v1-captain\n  failurePolicy: Fail\n  name: vcaptain-v1.kb.io\n  rules:\n  - apiGroups:\n    - crew.testproject.org\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - captains\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /validate-ship-testproject-org-v2alpha1-cruiser\n  failurePolicy: Fail\n  name: vcruiser-v2alpha1.kb.io\n  rules:\n  - apiGroups:\n    - ship.testproject.org\n    apiVersions:\n    - v2alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - cruisers\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /validate-apps-v1-deployment\n  failurePolicy: Fail\n  name: vdeployment-v1.kb.io\n  rules:\n  - apiGroups:\n    - apps\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - deployments\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /validate-example-com-testproject-org-v1alpha1-memcached\n  failurePolicy: Fail\n  name: vmemcached-v1alpha1.kb.io\n  rules:\n  - apiGroups:\n    - example.com.testproject.org\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - memcacheds\n  sideEffects: None\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-multigroup-webhook-service\n      namespace: project-v4-multigroup-system\n      path: /validate--v1-pod\n  failurePolicy: Fail\n  name: vpod-v1.kb.io\n  rules:\n  - apiGroups:\n    - \"\"\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - pods\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4-multigroup/go.mod",
    "content": "module sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/cert-manager/cert-manager v1.20.0\n\tgithub.com/onsi/ginkgo/v2 v2.28.0\n\tgithub.com/onsi/gomega v1.39.1\n\tk8s.io/api v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/client-go v0.35.2\n\tk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.25.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.13.0 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.22.4 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.4 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.25.4 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.1 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.9.1 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.17.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.1 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.40.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.7.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.1 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.5.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect\n\tgoogle.golang.org/grpc v1.79.1 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.2 // indirect\n\tk8s.io/apiserver v0.35.2 // indirect\n\tk8s.io/component-base v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.140.0 // indirect\n\tk8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect\n\tsigs.k8s.io/gateway-api v1.5.0 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "testdata/project-v4-multigroup/grafana/controller-resources-metrics.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(process_cpu_seconds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}[5m]) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller CPU Usage\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"process_resident_memory_bytes{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller Memory Usage\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"observability\",\n          \"value\": \"observability\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Resources-Metrics\",\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/grafana/controller-runtime-metrics.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 9,\n      \"panels\": [],\n      \"title\": \"Reconciliation Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 24,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"controller_runtime_active_workers{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{controller}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of workers in use\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliations per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 1\n      },\n      \"id\": 7,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total Reconciliation Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliation errors per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 1\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_errors_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reconciliation Error Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 11,\n      \"panels\": [],\n      \"title\": \"Work Queue Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 22,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"workqueue_depth{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"WorkQueue Depth\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds an item stays in workqueue before being requested\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"normal\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 10\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds For Items Stay In Queue (before being requested) (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 10\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_adds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Add Rate\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How many seconds of work has done that is in progress and hasn't been observed by work_duration.\\nLarge values indicate stuck threads.\\nOne can deduce the number of stuck threads by observing the rate at which this increases.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 23,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(workqueue_unfinished_work_seconds{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unfinished Seconds\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds processing an item from workqueue takes.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 18\n      },\n      \"id\": 19,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds Processing Items From WorkQueue (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of retries handled by workqueue\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 18\n      },\n      \"id\": 17,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_retries_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}} \",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Retries Rate\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": [\n            \"All\"\n          ],\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Runtime-Metrics\",\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/grafana/custom-metrics/config.yaml",
    "content": "---\ncustomMetrics:\n#  - metric: # Raw custom metric (required)\n#    type:   # Metric type: counter/gauge/histogram (required)\n#    expr:   # Prom_ql for the metric (optional)\n#    unit:   # Unit of measurement, examples: s,none,bytes,percent,etc. (optional)\n#\n#\n# Example:\n# ---\n# customMetrics:\n#   - metric: foo_bar\n#     unit: none\n#     type: histogram\n#   \texpr: histogram_quantile(0.90, sum by(instance, le) (rate(foo_bar{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])))\n"
  },
  {
    "path": "testdata/project-v4-multigroup/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/apps/deployment_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage apps\n\nimport (\n\t\"context\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// DeploymentReconciler reconciles a Deployment object\ntype DeploymentReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Deployment object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&appsv1.Deployment{}).\n\t\tNamed(\"apps-deployment\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/apps/deployment_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage apps\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n)\n\nvar _ = Describe(\"Deployment Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/apps/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage apps\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = appsv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"context\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// CertificateReconciler reconciles a Certificate object\ntype CertificateReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Certificate object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CertificateReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&certmanagerv1.Certificate{}).\n\t\tNamed(\"cert-manager-certificate\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/cert-manager/certificate_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n)\n\nvar _ = Describe(\"Certificate Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/cert-manager/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage certmanager\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = certmanagerv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/crew/captain_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage crew\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n)\n\n// CaptainReconciler reconciles a Captain object\ntype CaptainReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=crew.testproject.org,resources=captains/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Captain object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&crewv1.Captain{}).\n\t\tNamed(\"crew-captain\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/crew/captain_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage crew\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n)\n\nvar _ = Describe(\"Captain Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tcaptain := &crewv1.Captain{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Captain\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, captain)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &crewv1.Captain{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &crewv1.Captain{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Captain\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &CaptainReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/crew/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage crew\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = crewv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/tools/events\"\n\t\"k8s.io/utils/ptr\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n)\n\nconst busyboxFinalizer = \"example.com.testproject.org/finalizer\"\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableBusybox represents the status of the Deployment reconciliation\n\ttypeAvailableBusybox = \"Available\"\n\t// typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are yet to occur.\n\ttypeDegradedBusybox = \"Degraded\"\n)\n\n// BusyboxReconciler reconciles a Busybox object\ntype BusyboxReconciler struct {\n\tclient.Client\n\tScheme   *runtime.Scheme\n\tRecorder events.EventRecorder\n}\n\n// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen\n// when the command <make manifests> is executed.\n// To know more about markers see: https://book.kubebuilder.io/reference/markers.html\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the Busybox instance\n\t// The purpose is check if the Custom Resource for the Kind Busybox\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tbusybox := &examplecomv1alpha1.Busybox{}\n\terr := r.Get(ctx, req.NamespacedName, busybox)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Busybox resource not found, ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get busybox\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tif len(busybox.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the busybox Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Let's add a finalizer. Then, we can define some operations which should\n\t// occur before the custom resource is deleted.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers\n\tif !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {\n\t\tlog.Info(\"Adding finalizer for Busybox\")\n\t\tcontrollerutil.AddFinalizer(busybox, busyboxFinalizer)\n\t\tif err = r.Update(ctx, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to update custom resource to add finalizer\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the Busybox instance is marked to be deleted, which is\n\t// indicated by the deletion timestamp being set.\n\tisBusyboxMarkedToBeDeleted := busybox.GetDeletionTimestamp() != nil\n\tif isBusyboxMarkedToBeDeleted {\n\t\tif controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {\n\t\t\tlog.Info(\"Performing finalizer operations for Busybox before deleting CR\")\n\n\t\t\t// Let's add here a status \"Downgrade\" to reflect that this resource began its process to be terminated.\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox,\n\t\t\t\tStatus: metav1.ConditionUnknown, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Performing finalizer operations for the custom resource: %s \", busybox.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// Perform all operations required before removing the finalizer and allow\n\t\t\t// the Kubernetes API to remove the custom resource.\n\t\t\tr.doFinalizerOperationsForBusybox(busybox)\n\n\t\t\t// TODO(user): If you add operations to the doFinalizerOperationsForBusybox method\n\t\t\t// then you need to ensure that all worked fine before deleting and updating the Downgrade status\n\t\t\t// otherwise, you should requeue here.\n\n\t\t\t// Re-fetch the busybox Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox,\n\t\t\t\tStatus: metav1.ConditionTrue, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Finalizer operations for custom resource %s name were successfully accomplished\", busybox.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing finalizer for Busybox after successfully performing the operations\")\n\t\t\tif ok := controllerutil.RemoveFinalizer(busybox, busyboxFinalizer); !ok {\n\t\t\t\terr = fmt.Errorf(\"finalizer for Busybox was not removed\")\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tif err := r.Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForBusybox(busybox)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Busybox\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", busybox.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-triggered again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif busybox.Spec.Size != nil {\n\t\tdesiredReplicas = *busybox.Spec.Size\n\t}\n\n\t// The CRD API defines that the Busybox type have a BusyboxSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the busybox Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", busybox.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", busybox.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// finalizeBusybox will perform the required operations before delete the CR.\nfunc (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alpha1.Busybox) {\n\t// TODO(user): Add the cleanup steps that the operator\n\t// needs to do before the CR can be deleted. Examples\n\t// of finalizers include performing backups and deleting\n\t// resources that are not owned by this CR, like a PVC.\n\n\t// Note: It is not recommended to use finalizers with the purpose of deleting resources which are\n\t// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,\n\t// are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference.\n\t// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.\n\t// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/\n\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name,\n\t\tcr.Namespace)\n}\n\n// deploymentForBusybox returns a Busybox Deployment object\nfunc (r *BusyboxReconciler) deploymentForBusybox(\n\tbusybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) {\n\tls := labelsForBusybox()\n\n\t// Get the Operand image\n\timage, err := imageForBusybox()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      busybox.Name,\n\t\t\tNamespace: busybox.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: busybox.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t// TODO(user): Uncomment the following code to configure the nodeAffinity expression\n\t\t\t\t\t// according to the platforms which are supported by your solution. It is considered\n\t\t\t\t\t// best practice to support multiple architectures. build your manager image using the\n\t\t\t\t\t// makefile target docker-buildx. Also, you can use docker manifest inspect <image>\n\t\t\t\t\t// to check what are the platforms supported.\n\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n\t\t\t\t\t// Affinity: &corev1.Affinity{\n\t\t\t\t\t//\t NodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t//\t\t RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t//\t\t\t NodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t//\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t MatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/arch\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/os\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"linux\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t },\n\t\t\t\t\t//\t\t \t },\n\t\t\t\t\t//\t\t },\n\t\t\t\t\t//\t },\n\t\t\t\t\t// },\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\t// IMPORTANT: seccomProfile was introduced with Kubernetes 1.19\n\t\t\t\t\t\t// If you are looking for to produce solutions to be supported\n\t\t\t\t\t\t// on lower versions you must remove this option.\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"busybox\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(busybox, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n\n// labelsForBusybox returns the labels for selecting the resources\n// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\nfunc labelsForBusybox() map[string]string {\n\tvar imageTag string\n\timage, err := imageForBusybox()\n\tif err == nil {\n\t\timageTag = strings.Split(image, \":\")[1]\n\t}\n\treturn map[string]string{\n\t\t\"app.kubernetes.io/name\":       \"project-v4-multigroup\",\n\t\t\"app.kubernetes.io/version\":    imageTag,\n\t\t\"app.kubernetes.io/managed-by\": \"BusyboxController\",\n\t}\n}\n\n// imageForBusybox gets the Operand image which is managed by this controller\n// from the BUSYBOX_IMAGE environment variable defined in the config/manager/manager.yaml\nfunc imageForBusybox() (string, error) {\n\tvar imageEnvVar = \"BUSYBOX_IMAGE\"\n\timage, found := os.LookupEnv(imageEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"unable to find %s environment variable with the image\", imageEnvVar)\n\t}\n\treturn image, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\n// The whole idea is to be watching the resources that matter for the controller.\n// When a resource that the controller is interested in changes, the Watch triggers\n// the controller’s reconciliation loop, ensuring that the actual state of the resource\n// matches the desired state as defined in the controller’s logic.\n//\n// Notice how we configured the Manager to monitor events such as the creation, update,\n// or deletion of a Custom Resource (CR) of the Busybox kind, as well as any changes\n// to the Deployment that the controller manages and owns.\nfunc (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t// Watch the Busybox CR(s) and trigger reconciliation whenever it\n\t\t// is created, updated, or deleted\n\t\tFor(&examplecomv1alpha1.Busybox{}).\n\t\tNamed(\"example.com-busybox\").\n\t\t// Watch the Deployment managed by the BusyboxReconciler. If any changes occur to the Deployment\n\t\t// owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n\t\t// state aligns with the desired state. See that the ownerRef was set when the Deployment was created.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n)\n\nvar _ = Describe(\"Busybox controller\", func() {\n\tContext(\"Busybox controller test\", func() {\n\n\t\tconst BusyboxName = \"test-busybox\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      BusyboxName,\n\t\t\t\tNamespace: BusyboxName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      BusyboxName,\n\t\t\tNamespace: BusyboxName,\n\t\t}\n\t\tbusybox := &examplecomv1alpha1.Busybox{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Create(ctx, namespace)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Setting the Image ENV VAR which stores the Operand image\")\n\t\t\terr = os.Setenv(\"BUSYBOX_IMAGE\", \"example.com/image:test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating the custom resource for the Kind Busybox\")\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, busybox)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t// Let's mock our custom resource at the same way that we would\n\t\t\t\t// apply on the cluster the manifest under config/samples\n\t\t\t\tbusybox = &examplecomv1alpha1.Busybox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      BusyboxName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: examplecomv1alpha1.BusyboxSpec{\n\t\t\t\t\t\tSize: ptr.To(int32(1)),\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, busybox)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind Busybox\")\n\t\t\tfound := &examplecomv1alpha1.Busybox{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\n\t\t\tBy(\"Removing the Image ENV VAR which stores the Operand image\")\n\t\t\t_ = os.Unsetenv(\"BUSYBOX_IMAGE\")\n\t\t})\n\n\t\tIt(\"should successfully reconcile a custom resource for Busybox\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &examplecomv1alpha1.Busybox{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource created\")\n\t\t\tbusyboxReconciler := &BusyboxReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := busyboxReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = busyboxReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Busybox instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, busybox)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(busybox.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableBusybox)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableBusybox)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableBusybox)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableBusybox)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/tools/events\"\n\t\"k8s.io/utils/ptr\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n)\n\nconst memcachedFinalizer = \"example.com.testproject.org/finalizer\"\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableMemcached represents the status of the Deployment reconciliation\n\ttypeAvailableMemcached = \"Available\"\n\t// typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are yet to occur.\n\ttypeDegradedMemcached = \"Degraded\"\n)\n\n// MemcachedReconciler reconciles a Memcached object\ntype MemcachedReconciler struct {\n\tclient.Client\n\tScheme   *runtime.Scheme\n\tRecorder events.EventRecorder\n}\n\n// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen\n// when the command <make manifests> is executed.\n// To know more about markers see: https://book.kubebuilder.io/reference/markers.html\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the Memcached instance\n\t// The purpose is check if the Custom Resource for the Kind Memcached\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tmemcached := &examplecomv1alpha1.Memcached{}\n\terr := r.Get(ctx, req.NamespacedName, memcached)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Memcached resource not found, ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get memcached\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tif len(memcached.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the memcached Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Let's add a finalizer. Then, we can define some operations which should\n\t// occur before the custom resource is deleted.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers\n\tif !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) {\n\t\tlog.Info(\"Adding finalizer for Memcached\")\n\t\tcontrollerutil.AddFinalizer(memcached, memcachedFinalizer)\n\t\tif err = r.Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update custom resource to add finalizer\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the Memcached instance is marked to be deleted, which is\n\t// indicated by the deletion timestamp being set.\n\tisMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil\n\tif isMemcachedMarkedToBeDeleted {\n\t\tif controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) {\n\t\t\tlog.Info(\"Performing finalizer operations for Memcached before deleting CR\")\n\n\t\t\t// Let's add here a status \"Downgrade\" to reflect that this resource began its process to be terminated.\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached,\n\t\t\t\tStatus: metav1.ConditionUnknown, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Performing finalizer operations for the custom resource: %s \", memcached.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// Perform all operations required before removing the finalizer and allow\n\t\t\t// the Kubernetes API to remove the custom resource.\n\t\t\tr.doFinalizerOperationsForMemcached(memcached)\n\n\t\t\t// TODO(user): If you add operations to the doFinalizerOperationsForMemcached method\n\t\t\t// then you need to ensure that all worked fine before deleting and updating the Downgrade status\n\t\t\t// otherwise, you should requeue here.\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached,\n\t\t\t\tStatus: metav1.ConditionTrue, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Finalizer operations for custom resource %s name were successfully accomplished\", memcached.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing finalizer for Memcached after successfully performing the operations\")\n\t\t\tif ok := controllerutil.RemoveFinalizer(memcached, memcachedFinalizer); !ok {\n\t\t\t\terr = fmt.Errorf(\"finalizer for Memcached was not removed\")\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tif err := r.Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForMemcached(memcached)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Memcached\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-triggered again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif memcached.Spec.Size != nil {\n\t\tdesiredReplicas = *memcached.Spec.Size\n\t}\n\n\t// The CRD API defines that the Memcached type have a MemcachedSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", memcached.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// finalizeMemcached will perform the required operations before delete the CR.\nfunc (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1alpha1.Memcached) {\n\t// TODO(user): Add the cleanup steps that the operator\n\t// needs to do before the CR can be deleted. Examples\n\t// of finalizers include performing backups and deleting\n\t// resources that are not owned by this CR, like a PVC.\n\n\t// Note: It is not recommended to use finalizers with the purpose of deleting resources which are\n\t// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,\n\t// are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference.\n\t// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.\n\t// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/\n\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name,\n\t\tcr.Namespace)\n}\n\n// deploymentForMemcached returns a Memcached Deployment object\nfunc (r *MemcachedReconciler) deploymentForMemcached(\n\tmemcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) {\n\tls := labelsForMemcached()\n\n\t// Get the Operand image\n\timage, err := imageForMemcached()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      memcached.Name,\n\t\t\tNamespace: memcached.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: memcached.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t// TODO(user): Uncomment the following code to configure the nodeAffinity expression\n\t\t\t\t\t// according to the platforms which are supported by your solution. It is considered\n\t\t\t\t\t// best practice to support multiple architectures. build your manager image using the\n\t\t\t\t\t// makefile target docker-buildx. Also, you can use docker manifest inspect <image>\n\t\t\t\t\t// to check what are the platforms supported.\n\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n\t\t\t\t\t// Affinity: &corev1.Affinity{\n\t\t\t\t\t//\t NodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t//\t\t RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t//\t\t\t NodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t//\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t MatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/arch\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/os\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"linux\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t },\n\t\t\t\t\t//\t\t \t },\n\t\t\t\t\t//\t\t },\n\t\t\t\t\t//\t },\n\t\t\t\t\t// },\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\t// IMPORTANT: seccomProfile was introduced with Kubernetes 1.19\n\t\t\t\t\t\t// If you are looking for to produce solutions to be supported\n\t\t\t\t\t\t// on lower versions you must remove this option.\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"memcached\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tRunAsUser:                ptr.To(int64(1001)),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: memcached.Spec.ContainerPort,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"--memory-limit=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n\n// labelsForMemcached returns the labels for selecting the resources\n// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\nfunc labelsForMemcached() map[string]string {\n\tvar imageTag string\n\timage, err := imageForMemcached()\n\tif err == nil {\n\t\timageTag = strings.Split(image, \":\")[1]\n\t}\n\treturn map[string]string{\n\t\t\"app.kubernetes.io/name\":       \"project-v4-multigroup\",\n\t\t\"app.kubernetes.io/version\":    imageTag,\n\t\t\"app.kubernetes.io/managed-by\": \"MemcachedController\",\n\t}\n}\n\n// imageForMemcached gets the Operand image which is managed by this controller\n// from the MEMCACHED_IMAGE environment variable defined in the config/manager/manager.yaml\nfunc imageForMemcached() (string, error) {\n\tvar imageEnvVar = \"MEMCACHED_IMAGE\"\n\timage, found := os.LookupEnv(imageEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"unable to find %s environment variable with the image\", imageEnvVar)\n\t}\n\treturn image, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\n// The whole idea is to be watching the resources that matter for the controller.\n// When a resource that the controller is interested in changes, the Watch triggers\n// the controller’s reconciliation loop, ensuring that the actual state of the resource\n// matches the desired state as defined in the controller’s logic.\n//\n// Notice how we configured the Manager to monitor events such as the creation, update,\n// or deletion of a Custom Resource (CR) of the Memcached kind, as well as any changes\n// to the Deployment that the controller manages and owns.\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t// Watch the Memcached CR(s) and trigger reconciliation whenever it\n\t\t// is created, updated, or deleted\n\t\tFor(&examplecomv1alpha1.Memcached{}).\n\t\tNamed(\"example.com-memcached\").\n\t\t// Watch the Deployment managed by the MemcachedReconciler. If any changes occur to the Deployment\n\t\t// owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n\t\t// state aligns with the desired state. See that the ownerRef was set when the Deployment was created.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/memcached_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n)\n\nvar _ = Describe(\"Memcached controller\", func() {\n\tContext(\"Memcached controller test\", func() {\n\n\t\tconst MemcachedName = \"test-memcached\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      MemcachedName,\n\t\t\t\tNamespace: MemcachedName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      MemcachedName,\n\t\t\tNamespace: MemcachedName,\n\t\t}\n\t\tmemcached := &examplecomv1alpha1.Memcached{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Create(ctx, namespace)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Setting the Image ENV VAR which stores the Operand image\")\n\t\t\terr = os.Setenv(\"MEMCACHED_IMAGE\", \"example.com/image:test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating the custom resource for the Kind Memcached\")\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, memcached)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t// Let's mock our custom resource at the same way that we would\n\t\t\t\t// apply on the cluster the manifest under config/samples\n\t\t\t\tmemcached = &examplecomv1alpha1.Memcached{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      MemcachedName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: examplecomv1alpha1.MemcachedSpec{\n\t\t\t\t\t\tSize:          ptr.To(int32(1)),\n\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, memcached)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind Memcached\")\n\t\t\tfound := &examplecomv1alpha1.Memcached{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\n\t\t\tBy(\"Removing the Image ENV VAR which stores the Operand image\")\n\t\t\t_ = os.Unsetenv(\"MEMCACHED_IMAGE\")\n\t\t})\n\n\t\tIt(\"should successfully reconcile a custom resource for Memcached\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &examplecomv1alpha1.Memcached{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource created\")\n\t\t\tmemcachedReconciler := &MemcachedReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := memcachedReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = memcachedReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Memcached instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(memcached.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableMemcached)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableMemcached)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = examplecomv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n)\n\n// WordpressReconciler reconciles a Wordpress object\ntype WordpressReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=wordpresses,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=wordpresses/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,resources=wordpresses/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Wordpress object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *WordpressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *WordpressReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&examplecomv1.Wordpress{}).\n\t\tNamed(\"example.com-wordpress\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/example.com/wordpress_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage examplecom\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n)\n\nvar _ = Describe(\"Wordpress Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\twordpress := &examplecomv1.Wordpress{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Wordpress\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, wordpress)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &examplecomv1.Wordpress{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &examplecomv1.Wordpress{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Wordpress\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &WordpressReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/fiz/bar_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fiz\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tfizv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1\"\n)\n\n// BarReconciler reconciles a Bar object\ntype BarReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=fiz.testproject.org,resources=bars,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=fiz.testproject.org,resources=bars/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=fiz.testproject.org,resources=bars/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Bar object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *BarReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *BarReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&fizv1.Bar{}).\n\t\tNamed(\"fiz-bar\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/fiz/bar_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fiz\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tfizv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1\"\n)\n\nvar _ = Describe(\"Bar Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tbar := &fizv1.Bar{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Bar\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, bar)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &fizv1.Bar{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &fizv1.Bar{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Bar\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &BarReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/fiz/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage fiz\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tfizv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/fiz/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = fizv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo/bar_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foo\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tfoov1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1\"\n)\n\n// BarReconciler reconciles a Bar object\ntype BarReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=foo.testproject.org,resources=bars,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=foo.testproject.org,resources=bars/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=foo.testproject.org,resources=bars/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Bar object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *BarReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *BarReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&foov1.Bar{}).\n\t\tNamed(\"foo-bar\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo/bar_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foo\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tfoov1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1\"\n)\n\nvar _ = Describe(\"Bar Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tbar := &foov1.Bar{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Bar\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, bar)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &foov1.Bar{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &foov1.Bar{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Bar\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &BarReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foo\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tfoov1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = foov1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foopolicy\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tfoopolicyv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1\"\n)\n\n// HealthCheckPolicyReconciler reconciles a HealthCheckPolicy object\ntype HealthCheckPolicyReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=foo.policy.testproject.org,resources=healthcheckpolicies,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=foo.policy.testproject.org,resources=healthcheckpolicies/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=foo.policy.testproject.org,resources=healthcheckpolicies/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the HealthCheckPolicy object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *HealthCheckPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *HealthCheckPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&foopolicyv1.HealthCheckPolicy{}).\n\t\tNamed(\"foo.policy-healthcheckpolicy\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo.policy/healthcheckpolicy_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foopolicy\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tfoopolicyv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1\"\n)\n\nvar _ = Describe(\"HealthCheckPolicy Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\thealthcheckpolicy := &foopolicyv1.HealthCheckPolicy{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind HealthCheckPolicy\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, healthcheckpolicy)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &foopolicyv1.HealthCheckPolicy{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &foopolicyv1.HealthCheckPolicy{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance HealthCheckPolicy\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &HealthCheckPolicyReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/foo.policy/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage foopolicy\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tfoopolicyv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/foo.policy/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = foopolicyv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage seacreatures\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tseacreaturesv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta1\"\n)\n\n// KrakenReconciler reconciles a Kraken object\ntype KrakenReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=krakens,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=krakens/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=krakens/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Kraken object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *KrakenReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *KrakenReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&seacreaturesv1beta1.Kraken{}).\n\t\tNamed(\"sea-creatures-kraken\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/sea-creatures/kraken_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage seacreatures\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tseacreaturesv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta1\"\n)\n\nvar _ = Describe(\"Kraken Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tkraken := &seacreaturesv1beta1.Kraken{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Kraken\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, kraken)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &seacreaturesv1beta1.Kraken{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &seacreaturesv1beta1.Kraken{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Kraken\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &KrakenReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage seacreatures\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tseacreaturesv1beta2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta2\"\n)\n\n// LeviathanReconciler reconciles a Leviathan object\ntype LeviathanReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=leviathans,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=leviathans/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=leviathans/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Leviathan object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *LeviathanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *LeviathanReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&seacreaturesv1beta2.Leviathan{}).\n\t\tNamed(\"sea-creatures-leviathan\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/sea-creatures/leviathan_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage seacreatures\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tseacreaturesv1beta2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta2\"\n)\n\nvar _ = Describe(\"Leviathan Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tleviathan := &seacreaturesv1beta2.Leviathan{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Leviathan\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, leviathan)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &seacreaturesv1beta2.Leviathan{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &seacreaturesv1beta2.Leviathan{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Leviathan\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &LeviathanReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/sea-creatures/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage seacreatures\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tseacreaturesv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta1\"\n\tseacreaturesv1beta2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/sea-creatures/v1beta2\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = seacreaturesv1beta1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = seacreaturesv1beta2.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n)\n\n// CruiserReconciler reconciles a Cruiser object\ntype CruiserReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=cruisers,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=cruisers/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=cruisers/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Cruiser object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *CruiserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *CruiserReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&shipv2alpha1.Cruiser{}).\n\t\tNamed(\"ship-cruiser\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/cruiser_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n)\n\nvar _ = Describe(\"Cruiser Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tcruiser := &shipv2alpha1.Cruiser{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Cruiser\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, cruiser)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &shipv2alpha1.Cruiser{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &shipv2alpha1.Cruiser{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Cruiser\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &CruiserReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n)\n\n// DestroyerReconciler reconciles a Destroyer object\ntype DestroyerReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=destroyers,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=destroyers/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=destroyers/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Destroyer object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *DestroyerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *DestroyerReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&shipv1.Destroyer{}).\n\t\tNamed(\"ship-destroyer\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/destroyer_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n)\n\nvar _ = Describe(\"Destroyer Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tdestroyer := &shipv1.Destroyer{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Destroyer\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, destroyer)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &shipv1.Destroyer{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &shipv1.Destroyer{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Destroyer\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &DestroyerReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/frigate_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tshipv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1\"\n)\n\n// FrigateReconciler reconciles a Frigate object\ntype FrigateReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=frigates,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=frigates/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=ship.testproject.org,resources=frigates/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Frigate object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *FrigateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *FrigateReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&shipv1beta1.Frigate{}).\n\t\tNamed(\"ship-frigate\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/frigate_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tshipv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1\"\n)\n\nvar _ = Describe(\"Frigate Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\tfrigate := &shipv1beta1.Frigate{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Frigate\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, frigate)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &shipv1beta1.Frigate{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &shipv1beta1.Frigate{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Frigate\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &FrigateReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/controller/ship/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ship\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n\tshipv1beta1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1beta1\"\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = shipv1beta1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = shipv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = shipv2alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar deploymentlog = logf.Log.WithName(\"deployment-resource\")\n\n// SetupDeploymentWebhookWithManager registers the webhook for Deployment in the manager.\nfunc SetupDeploymentWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &appsv1.Deployment{}).\n\t\tWithDefaulter(&DeploymentCustomDefaulter{}).\n\t\tWithValidator(&DeploymentCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-apps-v1-deployment,mutating=true,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=mdeployment-v1.kb.io,admissionReviewVersions=v1\n\n// DeploymentCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Deployment when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype DeploymentCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Deployment.\nfunc (d *DeploymentCustomDefaulter) Default(_ context.Context, obj *appsv1.Deployment) error {\n\tdeploymentlog.Info(\"Defaulting for Deployment\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-apps-v1-deployment,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps,resources=deployments,verbs=create;update,versions=v1,name=vdeployment-v1.kb.io,admissionReviewVersions=v1\n\n// DeploymentCustomValidator struct is responsible for validating the Deployment resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype DeploymentCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateCreate(_ context.Context, obj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Deployment.\nfunc (v *DeploymentCustomValidator) ValidateDelete(_ context.Context, obj *appsv1.Deployment) (admission.Warnings, error) {\n\tdeploymentlog.Info(\"Validation for Deployment upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/apps/v1/deployment_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Deployment Webhook\", func() {\n\tvar (\n\t\tobj       *appsv1.Deployment\n\t\toldObj    *appsv1.Deployment\n\t\tdefaulter DeploymentCustomDefaulter\n\t\tvalidator DeploymentCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &appsv1.Deployment{}\n\t\toldObj = &appsv1.Deployment{}\n\t\tdefaulter = DeploymentCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = DeploymentCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Deployment under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Deployment under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/apps/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = appsv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupDeploymentWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar issuerlog = logf.Log.WithName(\"issuer-resource\")\n\n// SetupIssuerWebhookWithManager registers the webhook for Issuer in the manager.\nfunc SetupIssuerWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &certmanagerv1.Issuer{}).\n\t\tWithDefaulter(&IssuerCustomDefaulter{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-cert-manager-io-v1-issuer,mutating=true,failurePolicy=fail,sideEffects=None,groups=cert-manager.io,resources=issuers,verbs=create;update,versions=v1,name=missuer-v1.kb.io,admissionReviewVersions=v1\n\n// IssuerCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Issuer when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype IssuerCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Issuer.\nfunc (d *IssuerCustomDefaulter) Default(_ context.Context, obj *certmanagerv1.Issuer) error {\n\tissuerlog.Info(\"Defaulting for Issuer\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/issuer_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Issuer Webhook\", func() {\n\tvar (\n\t\tobj       *certmanagerv1.Issuer\n\t\toldObj    *certmanagerv1.Issuer\n\t\tdefaulter IssuerCustomDefaulter\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &certmanagerv1.Issuer{}\n\t\toldObj = &certmanagerv1.Issuer{}\n\t\tdefaulter = IssuerCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Issuer under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/cert-manager/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcertmanagerv1 \"github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = certmanagerv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupIssuerWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar podlog = logf.Log.WithName(\"pod-resource\")\n\n// SetupPodWebhookWithManager registers the webhook for Pod in the manager.\nfunc SetupPodWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &corev1.Pod{}).\n\t\tWithValidator(&PodCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate--v1-pod,mutating=false,failurePolicy=fail,sideEffects=None,groups=\"\",resources=pods,verbs=create;update,versions=v1,name=vpod-v1.kb.io,admissionReviewVersions=v1\n\n// PodCustomValidator struct is responsible for validating the Pod resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype PodCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Pod.\nfunc (v *PodCustomValidator) ValidateCreate(_ context.Context, obj *corev1.Pod) (admission.Warnings, error) {\n\tpodlog.Info(\"Validation for Pod upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Pod.\nfunc (v *PodCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *corev1.Pod) (admission.Warnings, error) {\n\tpodlog.Info(\"Validation for Pod upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Pod.\nfunc (v *PodCustomValidator) ValidateDelete(_ context.Context, obj *corev1.Pod) (admission.Warnings, error) {\n\tpodlog.Info(\"Validation for Pod upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/core/v1/pod_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Pod Webhook\", func() {\n\tvar (\n\t\tobj       *corev1.Pod\n\t\toldObj    *corev1.Pod\n\t\tvalidator PodCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &corev1.Pod{}\n\t\toldObj = &corev1.Pod{}\n\t\tvalidator = PodCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating or updating Pod under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/core/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = corev1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupPodWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar captainlog = logf.Log.WithName(\"captain-resource\")\n\n// SetupCaptainWebhookWithManager registers the webhook for Captain in the manager.\nfunc SetupCaptainWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &crewv1.Captain{}).\n\t\tWithDefaulter(&CaptainCustomDefaulter{}).\n\t\tWithValidator(&CaptainCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-captain,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=mcaptain-v1.kb.io,admissionReviewVersions=v1\n\n// CaptainCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Captain when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype CaptainCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Captain.\nfunc (d *CaptainCustomDefaulter) Default(_ context.Context, obj *crewv1.Captain) error {\n\tcaptainlog.Info(\"Defaulting for Captain\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-crew-testproject-org-v1-captain,mutating=false,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=captains,verbs=create;update,versions=v1,name=vcaptain-v1.kb.io,admissionReviewVersions=v1\n\n// CaptainCustomValidator struct is responsible for validating the Captain resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CaptainCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateCreate(_ context.Context, obj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Captain.\nfunc (v *CaptainCustomValidator) ValidateDelete(_ context.Context, obj *crewv1.Captain) (admission.Warnings, error) {\n\tcaptainlog.Info(\"Validation for Captain upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/crew/v1/captain_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Captain Webhook\", func() {\n\tvar (\n\t\tobj       *crewv1.Captain\n\t\toldObj    *crewv1.Captain\n\t\tdefaulter CaptainCustomDefaulter\n\t\tvalidator CaptainCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &crewv1.Captain{}\n\t\toldObj = &crewv1.Captain{}\n\t\tdefaulter = CaptainCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t\tvalidator = CaptainCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Captain under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n\tContext(\"When creating or updating Captain under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/crew/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tcrewv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/crew/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = crewv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCaptainWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupWordpressWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1/wordpress_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar wordpresslog = logf.Log.WithName(\"wordpress-resource\")\n\n// SetupWordpressWebhookWithManager registers the webhook for Wordpress in the manager.\nfunc SetupWordpressWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &examplecomv1.Wordpress{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1/wordpress_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Wordpress Webhook\", func() {\n\tvar (\n\t\tobj    *examplecomv1.Wordpress\n\t\toldObj *examplecomv1.Wordpress\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &examplecomv1.Wordpress{}\n\t\toldObj = &examplecomv1.Wordpress{}\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Wordpress under Conversion Webhook\", func() {\n\t\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t\t// Example:\n\t\t// It(\"Should convert the object correctly\", func() {\n\t\t//     convertedObj := &examplecomv1.Wordpress{}\n\t\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t\t//     Expect(convertedObj).ToNot(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar memcachedlog = logf.Log.WithName(\"memcached-resource\")\n\n// SetupMemcachedWebhookWithManager registers the webhook for Memcached in the manager.\nfunc SetupMemcachedWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &examplecomv1alpha1.Memcached{}).\n\t\tWithValidator(&MemcachedCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-example-com-testproject-org-v1alpha1-memcached,mutating=false,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=memcacheds,verbs=create;update,versions=v1alpha1,name=vmemcached-v1alpha1.kb.io,admissionReviewVersions=v1\n\n// MemcachedCustomValidator struct is responsible for validating the Memcached resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype MemcachedCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateCreate(_ context.Context, obj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateDelete(_ context.Context, obj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/memcached_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Memcached Webhook\", func() {\n\tvar (\n\t\tobj       *examplecomv1alpha1.Memcached\n\t\toldObj    *examplecomv1alpha1.Memcached\n\t\tvalidator MemcachedCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &examplecomv1alpha1.Memcached{}\n\t\toldObj = &examplecomv1alpha1.Memcached{}\n\t\tvalidator = MemcachedCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating or updating Memcached under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/example.com/v1alpha1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/example.com/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupMemcachedWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar destroyerlog = logf.Log.WithName(\"destroyer-resource\")\n\n// SetupDestroyerWebhookWithManager registers the webhook for Destroyer in the manager.\nfunc SetupDestroyerWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &shipv1.Destroyer{}).\n\t\tWithDefaulter(&DestroyerCustomDefaulter{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// +kubebuilder:webhook:path=/mutate-ship-testproject-org-v1-destroyer,mutating=true,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=destroyers,verbs=create;update,versions=v1,name=mdestroyer-v1.kb.io,admissionReviewVersions=v1\n\n// DestroyerCustomDefaulter struct is responsible for setting default values on the custom resource of the\n// Kind Destroyer when those are created or updated.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as it is used only for temporary operations and does not need to be deeply copied.\ntype DestroyerCustomDefaulter struct {\n\t// TODO(user): Add more fields as needed for defaulting\n}\n\n// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Destroyer.\nfunc (d *DestroyerCustomDefaulter) Default(_ context.Context, obj *shipv1.Destroyer) error {\n\tdestroyerlog.Info(\"Defaulting for Destroyer\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your defaulting logic.\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v1/destroyer_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Destroyer Webhook\", func() {\n\tvar (\n\t\tobj       *shipv1.Destroyer\n\t\toldObj    *shipv1.Destroyer\n\t\tdefaulter DestroyerCustomDefaulter\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &shipv1.Destroyer{}\n\t\toldObj = &shipv1.Destroyer{}\n\t\tdefaulter = DestroyerCustomDefaulter{}\n\t\tExpect(defaulter).NotTo(BeNil(), \"Expected defaulter to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Destroyer under Defaulting Webhook\", func() {\n\t\t// TODO (user): Add logic for defaulting webhooks\n\t\t// Example:\n\t\t// It(\"Should apply defaults when a required field is empty\", func() {\n\t\t//     By(\"simulating a scenario where defaults should be applied\")\n\t\t//     obj.SomeFieldWithDefault = \"\"\n\t\t//     By(\"calling the Default method to apply defaults\")\n\t\t//     defaulter.Default(ctx, obj)\n\t\t//     By(\"checking that the default values are set\")\n\t\t//     Expect(obj.SomeFieldWithDefault).To(Equal(\"default_value\"))\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tshipv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = shipv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupDestroyerWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar cruiserlog = logf.Log.WithName(\"cruiser-resource\")\n\n// SetupCruiserWebhookWithManager registers the webhook for Cruiser in the manager.\nfunc SetupCruiserWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &shipv2alpha1.Cruiser{}).\n\t\tWithValidator(&CruiserCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-ship-testproject-org-v2alpha1-cruiser,mutating=false,failurePolicy=fail,sideEffects=None,groups=ship.testproject.org,resources=cruisers,verbs=create;update,versions=v2alpha1,name=vcruiser-v2alpha1.kb.io,admissionReviewVersions=v1\n\n// CruiserCustomValidator struct is responsible for validating the Cruiser resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype CruiserCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser.\nfunc (v *CruiserCustomValidator) ValidateCreate(_ context.Context, obj *shipv2alpha1.Cruiser) (admission.Warnings, error) {\n\tcruiserlog.Info(\"Validation for Cruiser upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Cruiser.\nfunc (v *CruiserCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *shipv2alpha1.Cruiser) (admission.Warnings, error) {\n\tcruiserlog.Info(\"Validation for Cruiser upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Cruiser.\nfunc (v *CruiserCustomValidator) ValidateDelete(_ context.Context, obj *shipv2alpha1.Cruiser) (admission.Warnings, error) {\n\tcruiserlog.Info(\"Validation for Cruiser upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/cruiser_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Cruiser Webhook\", func() {\n\tvar (\n\t\tobj       *shipv2alpha1.Cruiser\n\t\toldObj    *shipv2alpha1.Cruiser\n\t\tvalidator CruiserCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &shipv2alpha1.Cruiser{}\n\t\toldObj = &shipv2alpha1.Cruiser{}\n\t\tvalidator = CruiserCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating or updating Cruiser under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-multigroup/internal/webhook/ship/v2alpha1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2alpha1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tshipv2alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/api/ship/v2alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = shipv2alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupCruiserWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project-v4-multigroup:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project-v4-multigroup e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n})\n\nvar _ = AfterSuite(func() {\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-v4-multigroup-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-v4-multigroup-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-v4-multigroup-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-v4-multigroup-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-v4-multigroup-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\t\t\"-l\", \"kubernetes.io/service-name=project-v4-multigroup-webhook-service\",\n\t\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t\t}\n\t\t\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the mutating webhook server is ready\")\n\t\t\tverifyMutatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-multigroup-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"MutatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Mutating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyMutatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should provisioned cert-manager\", func() {\n\t\t\tBy(\"validating that cert-manager has the certificate Secret\")\n\t\t\tverifyCertManager := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t\t\t_, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t\tEventually(verifyCertManager).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for mutating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for mutating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"mutatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-multigroup-mutating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tmwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(mwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for validating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for validating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-multigroup-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for Wordpress conversion webhook\", func() {\n\t\t\tBy(\"checking CA injection for Wordpress conversion webhook\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"customresourcedefinitions.apiextensions.k8s.io\",\n\t\t\t\t\t\"wordpresses.example.com.testproject.org\",\n\t\t\t\t\t\"-o\", \"go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "testdata/project-v4-multigroup/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.custom-gcl.yml",
    "content": "# This file configures golangci-lint with module plugins.\n# When you run 'make lint', it will automatically build a custom golangci-lint binary\n# with all the plugins listed below.\n#\n# See: https://golangci-lint.run/plugins/module-plugins/\nversion: v2.8.0\nplugins:\n  # logcheck validates structured logging calls and parameters (e.g., balanced key-value pairs)\n  - module: \"sigs.k8s.io/logtools\"\n    import: \"sigs.k8s.io/logtools/logcheck/gclplugin\"\n    version: latest\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Kubebuilder DevContainer\",\n  \"image\": \"golang:1.25\",\n  \"features\": {\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"moby\": false,\n      \"dockerDefaultAddressPool\": \"base=172.30.0.0/16,size=24\"\n    },\n    \"ghcr.io/devcontainers/features/git:1\": {},\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"upgradePackages\": true\n    }\n  },\n\n  \"runArgs\": [\"--privileged\", \"--init\"],\n\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"terminal.integrated.shell.linux\": \"/bin/bash\"\n      },\n      \"extensions\": [\n        \"ms-kubernetes-tools.vscode-kubernetes-tools\",\n        \"ms-azuretools.vscode-docker\"\n      ]\n    }\n  },\n\n  \"remoteEnv\": {\n    \"GO111MODULE\": \"on\"\n  },\n\n  \"onCreateCommand\": \"bash .devcontainer/post-install.sh\"\n}\n\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.devcontainer/post-install.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\necho \"====================================\"\necho \"Kubebuilder DevContainer Setup\"\necho \"====================================\"\n\n# Verify running as root (required for installing to /usr/local/bin and /etc)\nif [ \"$(id -u)\" -ne 0 ]; then\n  echo \"ERROR: This script must be run as root\"\n  exit 1\nfi\n\necho \"\"\necho \"Detecting system architecture...\"\n# Detect architecture using uname\nMACHINE=$(uname -m)\ncase \"${MACHINE}\" in\n  x86_64)\n    ARCH=\"amd64\"\n    ;;\n  aarch64|arm64)\n    ARCH=\"arm64\"\n    ;;\n  *)\n    echo \"WARNING: Unsupported architecture ${MACHINE}, defaulting to amd64\"\n    ARCH=\"amd64\"\n    ;;\nesac\necho \"Architecture: ${ARCH}\"\n\necho \"\"\necho \"------------------------------------\"\necho \"Setting up bash completion...\"\necho \"------------------------------------\"\n\nBASH_COMPLETIONS_DIR=\"/usr/share/bash-completion/completions\"\n\n# Enable bash-completion in root's .bashrc (devcontainer runs as root)\nif ! grep -q \"source /usr/share/bash-completion/bash_completion\" ~/.bashrc 2>/dev/null; then\n  echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc\n  echo \"Added bash-completion to .bashrc\"\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Installing development tools...\"\necho \"------------------------------------\"\n\n# Install kind\nif ! command -v kind &> /dev/null; then\n  echo \"Installing kind...\"\n  curl -Lo /usr/local/bin/kind \"https://kind.sigs.k8s.io/dl/latest/kind-linux-${ARCH}\"\n  chmod +x /usr/local/bin/kind\n  echo \"kind installed successfully\"\nfi\n\n# Generate kind bash completion\nif command -v kind &> /dev/null; then\n  if kind completion bash > \"${BASH_COMPLETIONS_DIR}/kind\" 2>/dev/null; then\n    echo \"kind completion installed\"\n  else\n    echo \"WARNING: Failed to generate kind completion\"\n  fi\nfi\n\n# Install kubebuilder\nif ! command -v kubebuilder &> /dev/null; then\n  echo \"Installing kubebuilder...\"\n  curl -Lo /usr/local/bin/kubebuilder \"https://go.kubebuilder.io/dl/latest/linux/${ARCH}\"\n  chmod +x /usr/local/bin/kubebuilder\n  echo \"kubebuilder installed successfully\"\nfi\n\n# Generate kubebuilder bash completion\nif command -v kubebuilder &> /dev/null; then\n  if kubebuilder completion bash > \"${BASH_COMPLETIONS_DIR}/kubebuilder\" 2>/dev/null; then\n    echo \"kubebuilder completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubebuilder completion\"\n  fi\nfi\n\n# Install kubectl\nif ! command -v kubectl &> /dev/null; then\n  echo \"Installing kubectl...\"\n  KUBECTL_VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)\n  curl -Lo /usr/local/bin/kubectl \"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl\"\n  chmod +x /usr/local/bin/kubectl\n  echo \"kubectl installed successfully\"\nfi\n\n# Generate kubectl bash completion\nif command -v kubectl &> /dev/null; then\n  if kubectl completion bash > \"${BASH_COMPLETIONS_DIR}/kubectl\" 2>/dev/null; then\n    echo \"kubectl completion installed\"\n  else\n    echo \"WARNING: Failed to generate kubectl completion\"\n  fi\nfi\n\n# Generate Docker bash completion\nif command -v docker &> /dev/null; then\n  if docker completion bash > \"${BASH_COMPLETIONS_DIR}/docker\" 2>/dev/null; then\n    echo \"docker completion installed\"\n  else\n    echo \"WARNING: Failed to generate docker completion\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Configuring Docker environment...\"\necho \"------------------------------------\"\n\n# Wait for Docker to be ready\necho \"Waiting for Docker to be ready...\"\nfor i in {1..30}; do\n  if docker info >/dev/null 2>&1; then\n    echo \"Docker is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 30 ]; then\n    echo \"WARNING: Docker not ready after 30s\"\n  fi\n  sleep 1\ndone\n\n# Create kind network (ignore if already exists)\nif ! docker network inspect kind >/dev/null 2>&1; then\n  if docker network create kind >/dev/null 2>&1; then\n    echo \"Created kind network\"\n  else\n    echo \"WARNING: Failed to create kind network (may already exist)\"\n  fi\nfi\n\necho \"\"\necho \"------------------------------------\"\necho \"Verifying installations...\"\necho \"------------------------------------\"\nkind version\nkubebuilder version\nkubectl version --client\ndocker --version\ngo version\n\necho \"\"\necho \"====================================\"\necho \"DevContainer ready!\"\necho \"====================================\"\necho \"All development tools installed successfully.\"\necho \"You can now start building Kubernetes operators.\"\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.dockerignore",
    "content": "# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file\n# Ignore everything by default and re-include only needed files\n**\n\n# Re-include Go source files (but not *_test.go)\n!**/*.go\n**/*_test.go\n\n# Re-include Go module files\n!go.mod\n!go.sum\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.github/workflows/auto_update.yml",
    "content": "name: Auto Update\n\n# The 'kubebuilder alpha update' command requires write access to the repository to create a branch\n# with the update files and allow you to open a pull request using the link provided in the issue.\n# The branch created will be named in the format kubebuilder-update-from-<from-version>-to-<to-version> by default.\n# To protect your codebase, please ensure that you have branch protection rules configured for your \n# main branches. This will guarantee that no one can bypass a review and push directly to a branch like 'main'.\npermissions:\n  contents: write  # Create and push the update branch\n  issues: write    # Create GitHub Issue with PR link\n  models: read     # Use GitHub Models for AI summaries\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * 2\" # Every Tuesday at 00:00 UTC\n\njobs:\n  auto-update:\n    runs-on: ubuntu-latest\n    env:\n      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    # Checkout the repository.\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        fetch-depth: 0\n\n    # Configure Git to create commits with the GitHub Actions bot.\n    - name: Configure Git\n      run: |\n        git config --global user.name \"github-actions[bot]\"\n        git config --global user.email \"github-actions[bot]@users.noreply.github.com\"\n\n    # Set up Go environment.\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: stable\n\n    # Install Kubebuilder.\n    - name: Install Kubebuilder\n      run: |\n        curl -L -o kubebuilder \"https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)\"\n        chmod +x kubebuilder\n        sudo mv kubebuilder /usr/local/bin/\n        kubebuilder version\n\n    # Install Models extension for GitHub CLI.\n    - name: Install gh-models extension\n      run: |\n        gh extension install github/gh-models --force\n        gh models --help >/dev/null\n\n    # Run the Kubebuilder alpha update command.\n    # More info: https://kubebuilder.io/reference/commands/alpha_update\n    - name: Run kubebuilder alpha update\n      # Executes the update command with specified flags.\n      # --force: Completes the merge even if conflicts occur, leaving conflict markers.\n      # --push: Automatically pushes the resulting output branch to the 'origin' remote.\n      # --restore-path: Preserves specified paths (e.g., CI workflow files) when squashing.\n      # --open-gh-issue: Creates a GitHub Issue with a link for opening a PR for review.\n      # --use-gh-models: Adds an AI-generated comment to the created Issue with\n      #   a short overview of the scaffold changes and conflict-resolution guidance (if any).\n      run: |\n        kubebuilder alpha update \\\n          --force \\\n          --push \\\n          --restore-path .github/workflows \\\n          --open-gh-issue \\\n          --use-gh-models\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check linter configuration\n        run: make lint-config\n      - name: Run linter\n        run: make lint\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.github/workflows/test-chart.yml",
    "content": "name: Test Chart\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Create kind cluster\n        run: kind create cluster\n\n      - name: Prepare project-v4-with-plugins\n        run: |\n          go mod tidy\n          make docker-build IMG=controller:latest\n          kind load docker-image controller:latest\n\n      - name: Install Helm\n        run: make install-helm\n\n      - name: Lint Helm Chart\n        run: |\n          helm lint ./dist/chart\n\n\n      - name: Install cert-manager via Helm (wait for readiness)\n        run: |\n          helm repo add jetstack https://charts.jetstack.io\n          helm repo update\n          helm install cert-manager jetstack/cert-manager \\\n            --namespace cert-manager \\\n            --create-namespace \\\n            --set crds.enabled=true \\\n            --wait \\\n            --timeout 300s\n\n# TODO: Uncomment if Prometheus is enabled\n#      - name: Install Prometheus Operator CRDs\n#        run: |\n#          helm repo add prometheus-community https://prometheus-community.github.io/helm-charts\n#          helm repo update\n#          helm install prometheus-crds prometheus-community/prometheus-operator-crds\n\n      - name: Deploy manager via Helm\n        run: |\n          make helm-deploy IMG=project-v4-with-plugins:v0.1.0\n\n      - name: Check Helm release status\n        run: |\n          make helm-status\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.github/workflows/test-e2e.yml",
    "content": "name: E2E Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test-e2e:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Install the latest version of kind\n        run: |\n          curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)\n          chmod +x ./kind\n          sudo mv ./kind /usr/local/bin/kind\n\n      - name: Verify kind installation\n        run: kind version\n\n      - name: Running Test e2e\n        run: |\n          go mod tidy\n          make test-e2e\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Run on Ubuntu\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone the code\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Running Tests\n        run: |\n          go mod tidy\n          make test\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin/*\nDockerfile.cross\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Go workspace file\ngo.work\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n.vscode\n*.swp\n*.swo\n*~\n\n# Kubeconfig might contain secrets\n*.kubeconfig\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/.golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\nlinters:\n  default: none\n  enable:\n    - copyloopvar\n    - dupl\n    - errcheck\n    - ginkgolinter\n    - goconst\n    - gocyclo\n    - govet\n    - ineffassign\n    - lll\n    - modernize\n    - misspell\n    - nakedret\n    - prealloc\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n    - logcheck\n  settings:\n    custom:\n      logcheck:\n        type: \"module\"\n        description: Checks Go logging calls for Kubernetes logging conventions.\n    revive:\n      rules:\n        - name: comment-spacings\n        - name: import-shadowing\n    modernize:\n      disable:\n        - omitzero\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - lll\n        path: api/*\n      - linters:\n          - dupl\n          - lll\n        path: internal/*\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofmt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/AGENTS.md",
    "content": "# project-v4-with-plugins - AI Agent Guide\n\n## Project Structure\n\n**Single-group layout (default):**\n```\ncmd/main.go                    Manager entry (registers controllers/webhooks)\napi/<version>/*_types.go       CRD schemas (+kubebuilder markers)\napi/<version>/zz_generated.*   Auto-generated (DO NOT EDIT)\ninternal/controller/*          Reconciliation logic\ninternal/webhook/*             Validation/defaulting (if present)\nconfig/crd/bases/*             Generated CRDs (DO NOT EDIT)\nconfig/rbac/role.yaml          Generated RBAC (DO NOT EDIT)\nconfig/samples/*               Example CRs (edit these)\nMakefile                       Build/test/deploy commands\nPROJECT                        Kubebuilder metadata Auto-generated (DO NOT EDIT)\n```\n\n**Multi-group layout** (for projects with multiple API groups):\n```\napi/<group>/<version>/*_types.go       CRD schemas by group\ninternal/controller/<group>/*          Controllers by group\ninternal/webhook/<group>/<version>/*   Webhooks by group and version (if present)\n```\n\nMulti-group layout organizes APIs by group name (e.g., `batch`, `apps`). Check the `PROJECT` file for `multigroup: true`.\n\n**To convert to multi-group layout:**\n1. Run: `kubebuilder edit --multigroup=true`\n2. Move APIs: `mkdir -p api/<group> && mv api/<version> api/<group>/`\n3. Move controllers: `mkdir -p internal/controller/<group> && mv internal/controller/*.go internal/controller/<group>/`\n4. Move webhooks (if present): `mkdir -p internal/webhook/<group> && mv internal/webhook/<version> internal/webhook/<group>/`\n5. Update import paths in all files\n6. Fix `path` in `PROJECT` file for each resource\n7. Update test suite CRD paths (add one more `..` to relative paths)\n\n## Critical Rules\n\n### Never Edit These (Auto-Generated)\n- `config/crd/bases/*.yaml` - from `make manifests`\n- `config/rbac/role.yaml` - from `make manifests`\n- `config/webhook/manifests.yaml` - from `make manifests`\n- `**/zz_generated.*.go` - from `make generate`\n- `PROJECT` - from `kubebuilder [OPTIONS]`\n\n### Never Remove Scaffold Markers\nDo NOT delete `// +kubebuilder:scaffold:*` comments. CLI injects code at these markers.\n\n### Keep Project Structure\nDo not move files around. The CLI expects files in specific locations.\n\n### Always Use CLI Commands\nAlways use `kubebuilder create api` and `kubebuilder create webhook` to scaffold. Do NOT create files manually.\n\n### E2E Tests Require an Isolated Kind Cluster\nThe e2e tests are designed to validate the solution in an isolated environment (similar to GitHub Actions CI).\nEnsure you run them against a dedicated [Kind](https://kind.sigs.k8s.io/) cluster (not your “real” dev/prod cluster).\n\n## After Making Changes\n\n**After editing `*_types.go` or markers:**\n```\nmake manifests  # Regenerate CRDs/RBAC from markers\nmake generate   # Regenerate DeepCopy methods\n```\n\n**After editing `*.go` files:**\n```\nmake lint-fix   # Auto-fix code style\nmake test       # Run unit tests\n```\n\n## CLI Commands Cheat Sheet\n\n### Create API (your own types)\n```bash\nkubebuilder create api --group <group> --version <version> --kind <Kind>\n```\n\n### Deploy Image Plugin (scaffold to deploy/manage ANY container image)\n\nGenerate a controller that deploys and manages a container image (nginx, redis, memcached, your app, etc.):\n\n```bash\n# Example: deploying memcached\nkubebuilder create api --group example.com --version v1alpha1 --kind Memcached \\\n  --image=memcached:alpine \\\n  --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nScaffolds good-practice code: reconciliation logic, status conditions, finalizers, RBAC. Use as a reference implementation.\n\n\n### Create Webhooks\n```bash\n# Validation + defaulting\nkubebuilder create webhook --group <group> --version <version> --kind <Kind> \\\n  --defaulting --programmatic-validation\n\n# Conversion webhook (for multi-version APIs)\nkubebuilder create webhook --group <group> --version v1 --kind <Kind> \\\n  --conversion --spoke v2\n```\n\n### Controller for Core Kubernetes Types\n```bash\n# Watch Pods\nkubebuilder create api --group core --version v1 --kind Pod \\\n  --controller=true --resource=false\n\n# Watch Deployments\nkubebuilder create api --group apps --version v1 --kind Deployment \\\n  --controller=true --resource=false\n```\n\n### Controller for External Types (e.g., from other operators)\n\nWatch resources from external APIs (cert-manager, Argo CD, Istio, etc.):\n\n```bash\n# Example: watching cert-manager Certificate resources\nkubebuilder create api \\\n  --group cert-manager --version v1 --kind Certificate \\\n  --controller=true --resource=false \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n**Note:** Use `--external-api-module=<module>@<version>` only if you need a specific version. Otherwise, omit `@<version>` to use what's in go.mod.\n\n### Webhook for External Types\n\n```bash\n# Example: validating external resources\nkubebuilder create webhook \\\n  --group cert-manager --version v1 --kind Issuer \\\n  --defaulting \\\n  --external-api-path=github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1 \\\n  --external-api-domain=io \\\n  --external-api-module=github.com/cert-manager/cert-manager\n```\n\n## Testing & Development\n\n```bash\nmake test              # Run unit tests (uses envtest: real K8s API + etcd)\nmake run               # Run locally (uses current kubeconfig context)\n```\n\nTests use **Ginkgo + Gomega** (BDD style). Check `suite_test.go` for setup.\n\n## Deployment Workflow\n\n```bash\n# 1. Regenerate manifests\nmake manifests generate\n\n# 2. Build & deploy\nexport IMG=<registry>/<project>:tag\nmake docker-build docker-push IMG=$IMG  # Or: kind load docker-image $IMG --name <cluster>\nmake deploy IMG=$IMG\n\n# 3. Test\nkubectl apply -k config/samples/\n\n# 4. Debug\nkubectl logs -n <project>-system deployment/<project>-controller-manager -c manager -f\n```\n\n### API Design\n\n**Key markers for** `api/<version>/*_types.go`:\n\n```go\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:resource:scope=Namespaced\n// +kubebuilder:printcolumn:name=\"Status\",type=string,JSONPath=\".status.conditions[?(@.type=='Ready')].status\"\n\n// On fields:\n// +kubebuilder:validation:Required\n// +kubebuilder:validation:Minimum=1\n// +kubebuilder:validation:MaxLength=100\n// +kubebuilder:validation:Pattern=\"^[a-z]+$\"\n// +kubebuilder:default=\"value\"\n```\n\n- **Use** `metav1.Condition` for status (not custom string fields)\n- **Use predefined types**: `metav1.Time` instead of `string` for dates\n- **Follow K8s API conventions**: Standard field names (`spec`, `status`, `metadata`)\n\n### Controller Design\n\n**RBAC markers in** `internal/controller/*_controller.go`:\n\n```go\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=mygroup.example.com,resources=mykinds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n```\n\n**Implementation rules:**\n- **Idempotent reconciliation**: Safe to run multiple times\n- **Re-fetch before updates**: `r.Get(ctx, req.NamespacedName, obj)` before `r.Update` to avoid conflicts\n- **Structured logging**: `log := log.FromContext(ctx); log.Info(\"msg\", \"key\", val)`\n- **Owner references**: Enable automatic garbage collection (`SetControllerReference`)\n- **Watch secondary resources**: Use `.Owns()` or `.Watches()`, not just `RequeueAfter`\n- **Finalizers**: Clean up external resources (buckets, VMs, DNS entries)\n\n### Logging\n\n**Follow Kubernetes logging message style guidelines:**\n\n- Start from a capital letter\n- Do not end the message with a period\n- Active voice: subject present (`\"Deployment could not create Pod\"`) or omitted (`\"Could not create Pod\"`)\n- Past tense: `\"Could not delete Pod\"` not `\"Cannot delete Pod\"`\n- Specify object type: `\"Deleted Pod\"` not `\"Deleted\"`\n- Balanced key-value pairs\n\n```go\nlog.Info(\"Starting reconciliation\")\nlog.Info(\"Created Deployment\", \"name\", deploy.Name)\nlog.Error(err, \"Failed to create Pod\", \"name\", name)\n```\n\n**Reference:** https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines\n\n### Webhooks\n- **Create all types together**: `--defaulting --programmatic-validation --conversion`\n- **When`--force`is used**: Backup custom logic first, then restore after scaffolding\n- **For multi-version APIs**: Use hub-and-spoke pattern (`--conversion --spoke v2`)\n  - Hub version: Usually oldest stable version (v1)\n  - Spoke versions: Newer versions that convert to/from hub (v2, v3)\n  - Example: `--group crew --version v1 --kind Captain --conversion --spoke v2` (v1 is hub, v2 is spoke)\n\n### Learning from Examples\n\nThe **deploy-image plugin** scaffolds a complete controller following good practices. Use it as a reference implementation:\n\n```bash\nkubebuilder create api --group example --version v1alpha1 --kind MyApp \\\n  --image=<your-image> --plugins=deploy-image.go.kubebuilder.io/v1-alpha\n```\n\nGenerated code includes: status conditions (`metav1.Condition`), finalizers, owner references, events, idempotent reconciliation.\n\n## Distribution Options\n\n### Option 1: YAML Bundle (Kustomize)\n\n```bash\n# Generate dist/install.yaml from Kustomize manifests\nmake build-installer IMG=<registry>/<project>:tag\n```\n\n**Key points:**\n- The `dist/install.yaml` is generated from Kustomize manifests (CRDs, RBAC, Deployment)\n- Commit this file to your repository for easy distribution\n- Users only need `kubectl` to install (no additional tools required)\n\n**Example:** Users install with a single command:\n```bash\nkubectl apply -f https://raw.githubusercontent.com/<org>/<repo>/<tag>/dist/install.yaml\n```\n\n### Option 2: Helm Chart\n\n```bash\nkubebuilder edit --plugins=helm/v2-alpha                      # Generates dist/chart/ (default)\nkubebuilder edit --plugins=helm/v2-alpha --output-dir=charts  # Generates charts/chart/\n```\n\n**For development:**\n```bash\nmake helm-deploy IMG=<registry>/<project>:<tag>          # Deploy manager via Helm\nmake helm-deploy IMG=$IMG HELM_EXTRA_ARGS=\"--set ...\"    # Deploy with custom values\nmake helm-status                                         # Show release status\nmake helm-uninstall                                      # Remove release\nmake helm-history                                        # View release history\nmake helm-rollback                                       # Rollback to previous version\n```\n\n**For end users/production:**\n```bash\nhelm install my-release ./<output-dir>/chart/ --namespace <ns> --create-namespace\n```\n\n**Important:** If you add webhooks or modify manifests after initial chart generation:\n1. Backup any customizations in `<output-dir>/chart/values.yaml` and `<output-dir>/chart/manager/manager.yaml`\n2. Re-run: `kubebuilder edit --plugins=helm/v2-alpha --force` (use same `--output-dir` if customized)\n3. Manually restore your custom values from the backup\n\n### Publish Container Image\n\n```bash\nexport IMG=<registry>/<project>:<version>\nmake docker-build docker-push IMG=$IMG\n```\n\n## References\n\n### Essential Reading\n- **Kubebuilder Book**: https://book.kubebuilder.io (comprehensive guide)\n- **controller-runtime FAQ**: https://github.com/kubernetes-sigs/controller-runtime/blob/main/FAQ.md (common patterns and questions)\n- **Good Practices**: https://book.kubebuilder.io/reference/good-practices.html (why reconciliation is idempotent, status conditions, etc.)\n- **Logging Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#message-style-guidelines (message style, verbosity levels)\n\n### API Design & Implementation\n- **API Conventions**: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md\n- **Operator Pattern**: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n- **Markers Reference**: https://book.kubebuilder.io/reference/markers.html\n\n### Tools & Libraries\n- **controller-runtime**: https://github.com/kubernetes-sigs/controller-runtime\n- **controller-tools**: https://github.com/kubernetes-sigs/controller-tools\n- **Kubebuilder Repo**: https://github.com/kubernetes-sigs/kubebuilder\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.25 AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the Go source (relies on .dockerignore to filter)\nCOPY . .\n\n# Build\n# the GOARCH has no default value to allow the binary to be built according to the host where the command\n# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO\n# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,\n# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.\nRUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM gcr.io/distroless/static:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/Makefile",
    "content": "# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# CONTAINER_TOOL defines the container tool to be used for building images.\n# Be aware that the target commands are only tested with Docker which is\n# scaffolded by default. However, you might want to replace it to use other\n# tools. (i.e. podman)\nCONTAINER_TOOL ?= docker\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk command is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t\"$(CONTROLLER_GEN)\" rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t\"$(CONTROLLER_GEN)\" object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet setup-envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell \"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path)\" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out\n\n# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.\n# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.\n# CertManager is installed by default; skip with:\n# - CERT_MANAGER_INSTALL_SKIP=true\nKIND_CLUSTER ?= project-v4-with-plugins-test-e2e\n\n.PHONY: setup-test-e2e\nsetup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist\n\t@command -v $(KIND) >/dev/null 2>&1 || { \\\n\t\techo \"Kind is not installed. Please install Kind manually.\"; \\\n\t\texit 1; \\\n\t}\n\t@case \"$$($(KIND) get clusters)\" in \\\n\t\t*\"$(KIND_CLUSTER)\"*) \\\n\t\t\techo \"Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation.\" ;; \\\n\t\t*) \\\n\t\t\techo \"Creating Kind cluster '$(KIND_CLUSTER)'...\"; \\\n\t\t\t$(KIND) create cluster --name $(KIND_CLUSTER) ;; \\\n\tesac\n\n.PHONY: test-e2e\ntest-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.\n\tKIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e ./test/e2e/ -v -ginkgo.v\n\t$(MAKE) cleanup-test-e2e\n\n.PHONY: cleanup-test-e2e\ncleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests\n\t@$(KIND) delete cluster --name $(KIND_CLUSTER)\n\n.PHONY: lint\nlint: golangci-lint ## Run golangci-lint linter\n\t\"$(GOLANGCI_LINT)\" run\n\n.PHONY: lint-fix\nlint-fix: golangci-lint ## Run golangci-lint linter and perform fixes\n\t\"$(GOLANGCI_LINT)\" run --fix\n\n.PHONY: lint-config\nlint-config: golangci-lint ## Verify golangci-lint linter configuration\n\t\"$(GOLANGCI_LINT)\" config verify\n\n##@ Build\n\n.PHONY: build\nbuild: manifests generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager cmd/main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./cmd/main.go\n\n# If you wish to build the manager image targeting other platforms you can use the --platform flag.\n# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.\n# More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n.PHONY: docker-build\ndocker-build: ## Build docker image with the manager.\n\t$(CONTAINER_TOOL) build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\t$(CONTAINER_TOOL) push ${IMG}\n\n# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple\n# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:\n# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/\n# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/\n# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)\n# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.\nPLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le\n.PHONY: docker-buildx\ndocker-buildx: ## Build and push docker image for the manager for cross-platform support\n\t# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile\n\tsed -e '1 s/\\(^FROM\\)/FROM --platform=\\$$\\{BUILDPLATFORM\\}/; t' -e ' 1,// s//FROM --platform=\\$$\\{BUILDPLATFORM\\}/' Dockerfile > Dockerfile.cross\n\t- $(CONTAINER_TOOL) buildx create --name project-v4-with-plugins-builder\n\t$(CONTAINER_TOOL) buildx use project-v4-with-plugins-builder\n\t- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .\n\t- $(CONTAINER_TOOL) buildx rm project-v4-with-plugins-builder\n\trm Dockerfile.cross\n\n.PHONY: build-installer\nbuild-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.\n\tmkdir -p dist\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default > dist/install.yaml\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" apply -f -; else echo \"No CRDs to install; skipping.\"; fi\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t@out=\"$$( \"$(KUSTOMIZE)\" build config/crd 2>/dev/null || true )\"; \\\n\tif [ -n \"$$out\" ]; then echo \"$$out\" | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -; else echo \"No CRDs to delete; skipping.\"; fi\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && \"$(KUSTOMIZE)\" edit set image controller=${IMG}\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" apply -f -\n\n.PHONY: undeploy\nundeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t\"$(KUSTOMIZE)\" build config/default | \"$(KUBECTL)\" delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p \"$(LOCALBIN)\"\n\n## Tool Binaries\nKUBECTL ?= kubectl\nKIND ?= kind\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\nGOLANGCI_LINT = $(LOCALBIN)/golangci-lint\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v5.8.1\nCONTROLLER_TOOLS_VERSION ?= v0.20.1\n\n#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)\nENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_VERSION manually (controller-runtime replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?([0-9]+)\\.([0-9]+).*/release-\\1.\\2/')\n\n#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)\nENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \\\n  [ -n \"$$v\" ] || { echo \"Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)\" >&2; exit 1; }; \\\n  printf '%s\\n' \"$$v\" | sed -E 's/^v?[0-9]+\\.([0-9]+).*/1.\\1/')\n\nGOLANGCI_LINT_VERSION ?= v2.8.0\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\t$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\t$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))\n\n.PHONY: setup-envtest\nsetup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.\n\t@echo \"Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)...\"\n\t@\"$(ENVTEST)\" use $(ENVTEST_K8S_VERSION) --bin-dir \"$(LOCALBIN)\" -p path || { \\\n\t\techo \"Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION).\"; \\\n\t\texit 1; \\\n\t}\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download setup-envtest locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\t$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))\n\n.PHONY: golangci-lint\ngolangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.\n$(GOLANGCI_LINT): $(LOCALBIN)\n\t$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))\n\t@test -f .custom-gcl.yml && { \\\n\t\techo \"Building custom golangci-lint with plugins...\" && \\\n\t\t$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \\\n\t\tmv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \\\n\t} || true\n\n# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist\n# $1 - target path with name of binary\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f \"$(1)-$(3)\" ] && [ \"$$(readlink -- \"$(1)\" 2>/dev/null)\" = \"$(1)-$(3)\" ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nrm -f \"$(1)\" ;\\\nGOBIN=\"$(LOCALBIN)\" go install $${package} ;\\\nmv \"$(LOCALBIN)/$$(basename \"$(1)\")\" \"$(1)-$(3)\" ;\\\n} ;\\\nln -sf \"$$(realpath \"$(1)-$(3)\")\" \"$(1)\"\nendef\n\ndefine gomodver\n$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)\nendef\n\n##@ Helm Deployment\n\n## Helm binary to use for deploying the chart\nHELM ?= helm\n## Namespace to deploy the Helm release\nHELM_NAMESPACE ?= project-v4-with-plugins-system\n## Name of the Helm release\nHELM_RELEASE ?= project-v4-with-plugins\n## Path to the Helm chart directory\nHELM_CHART_DIR ?= dist/chart\n## Additional arguments to pass to helm commands\nHELM_EXTRA_ARGS ?=\n\n.PHONY: install-helm\ninstall-helm: ## Install the latest version of Helm.\n\t@command -v $(HELM) >/dev/null 2>&1 || { \\\n\t\techo \"Installing Helm...\" && \\\n\t\tcurl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \\\n\t}\n\n.PHONY: helm-deploy\nhelm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.\n\t$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \\\n\t\t--namespace $(HELM_NAMESPACE) \\\n\t\t--create-namespace \\\n\t\t--set manager.image.repository=$${IMG%:*} \\\n\t\t--set manager.image.tag=$${IMG##*:} \\\n\t\t--wait \\\n\t\t--timeout 5m \\\n\t\t$(HELM_EXTRA_ARGS)\n\n.PHONY: helm-uninstall\nhelm-uninstall: ## Uninstall the Helm release from the K8s cluster.\n\t$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-status\nhelm-status: ## Show Helm release status.\n\t$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-history\nhelm-history: ## Show Helm release history.\n\t$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n\n.PHONY: helm-rollback\nhelm-rollback: ## Rollback to previous Helm release.\n\t$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/PROJECT",
    "content": "# Code generated by tool. DO NOT EDIT.\n# This file is used to track the info used to scaffold your project\n# and allow the plugins properly work.\n# More info: https://book.kubebuilder.io/reference/project-config.html\ncliVersion: (devel)\ndomain: testproject.org\nlayout:\n- go.kubebuilder.io/v4\nnamespaced: true\nplugins:\n  autoupdate.kubebuilder.io/v1-alpha:\n    useGHModels: true\n  deploy-image.go.kubebuilder.io/v1-alpha:\n    resources:\n    - domain: testproject.org\n      group: example.com\n      kind: Memcached\n      options:\n        containerCommand: memcached,--memory-limit=64,-o,modern,-v\n        containerPort: \"11211\"\n        image: memcached:1.6.26-alpine3.19\n        runAsUser: \"1001\"\n      version: v1alpha1\n    - domain: testproject.org\n      group: example.com\n      kind: Busybox\n      options:\n        image: busybox:1.36.1\n      version: v1alpha1\n  grafana.kubebuilder.io/v1-alpha: {}\n  helm.kubebuilder.io/v2-alpha:\n    manifests: dist/install.yaml\n    output: dist\nprojectName: project-v4-with-plugins\nrepo: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins\nresources:\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Memcached\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\n  version: v1alpha1\n  webhooks:\n    validation: true\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Busybox\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\n  version: v1alpha1\n- api:\n    crdVersion: v1\n    namespaced: true\n  controller: true\n  domain: testproject.org\n  group: example.com\n  kind: Wordpress\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\n  version: v1\n  webhooks:\n    conversion: true\n    spoke:\n    - v2\n    webhookVersion: v1\n- api:\n    crdVersion: v1\n    namespaced: true\n  domain: testproject.org\n  group: example.com\n  kind: Wordpress\n  path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v2\n  version: v2\nversion: \"3\"\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/README.md",
    "content": "# project-v4-with-plugins\n// TODO(user): Add simple overview of use/purpose\n\n## Description\n// TODO(user): An in-depth paragraph about your project and overview of use\n\n## Getting Started\n\n### Prerequisites\n- go version v1.24.6+\n- docker version 17.03+.\n- kubectl version v1.11.3+.\n- Access to a Kubernetes v1.11.3+ cluster.\n\n### To Deploy on the cluster\n**Build and push your image to the location specified by `IMG`:**\n\n```sh\nmake docker-build docker-push IMG=<some-registry>/project-v4-with-plugins:tag\n```\n\n**NOTE:** This image ought to be published in the personal registry you specified.\nAnd it is required to have access to pull the image from the working environment.\nMake sure you have the proper permission to the registry if the above commands don’t work.\n\n**Install the CRDs into the cluster:**\n\n```sh\nmake install\n```\n\n**Deploy the Manager to the cluster with the image specified by `IMG`:**\n\n```sh\nmake deploy IMG=<some-registry>/project-v4-with-plugins:tag\n```\n\n> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin\nprivileges or be logged in as admin.\n\n**Create instances of your solution**\nYou can apply the samples (examples) from the config/sample:\n\n```sh\nkubectl apply -k config/samples/\n```\n\n>**NOTE**: Ensure that the samples has default values to test it out.\n\n### To Uninstall\n**Delete the instances (CRs) from the cluster:**\n\n```sh\nkubectl delete -k config/samples/\n```\n\n**Delete the APIs(CRDs) from the cluster:**\n\n```sh\nmake uninstall\n```\n\n**UnDeploy the controller from the cluster:**\n\n```sh\nmake undeploy\n```\n\n## Project Distribution\n\nFollowing the options to release and provide this solution to the users.\n\n### By providing a bundle with all YAML files\n\n1. Build the installer for the image built and published in the registry:\n\n```sh\nmake build-installer IMG=<some-registry>/project-v4-with-plugins:tag\n```\n\n**NOTE:** The makefile target mentioned above generates an 'install.yaml'\nfile in the dist directory. This file contains all the resources built\nwith Kustomize, which are necessary to install this project without its\ndependencies.\n\n2. Using the installer\n\nUsers can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install\nthe project, i.e.:\n\n```sh\nkubectl apply -f https://raw.githubusercontent.com/<org>/project-v4-with-plugins/<tag or branch>/dist/install.yaml\n```\n\n### By providing a Helm Chart\n\n1. Build the chart using the optional helm plugin\n\n```sh\nkubebuilder edit --plugins=helm/v2-alpha\n```\n\n2. See that a chart was generated under 'dist/chart', and users\ncan obtain this solution from there.\n\n**NOTE:** If you change the project, you need to update the Helm Chart\nusing the same command above to sync the latest changes. Furthermore,\nif you create webhooks, you need to use the above command with\nthe '--force' flag and manually ensure that any custom configuration\npreviously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'\nis manually re-applied afterwards.\n\n## Contributing\n// TODO(user): Add detailed information on how you would like others to contribute to this project\n\n**NOTE:** Run `make help` for more information on all potential `make` targets\n\nMore information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)\n\n## License\n\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1 contains API Schema definitions for the example.com v1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1/wordpress_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// Hub marks this type as a conversion hub.\nfunc (*Wordpress) Hub() {}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1/wordpress_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// WordpressSpec defines the desired state of Wordpress\ntype WordpressSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Wordpress. Edit wordpress_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// WordpressStatus defines the observed state of Wordpress.\ntype WordpressStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Wordpress resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:storageversion\n// +kubebuilder:subresource:status\n\n// Wordpress is the Schema for the wordpresses API\ntype Wordpress struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Wordpress\n\t// +required\n\tSpec WordpressSpec `json:\"spec\"`\n\n\t// status defines the observed state of Wordpress\n\t// +optional\n\tStatus WordpressStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// WordpressList contains a list of Wordpress\ntype WordpressList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Wordpress `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Wordpress{}, &WordpressList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Wordpress) DeepCopyInto(out *Wordpress) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wordpress.\nfunc (in *Wordpress) DeepCopy() *Wordpress {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Wordpress)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Wordpress) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressList) DeepCopyInto(out *WordpressList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Wordpress, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressList.\nfunc (in *WordpressList) DeepCopy() *WordpressList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *WordpressList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec.\nfunc (in *WordpressSpec) DeepCopy() *WordpressSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressStatus) DeepCopyInto(out *WordpressStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]metav1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressStatus.\nfunc (in *WordpressStatus) DeepCopy() *WordpressStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1alpha1/busybox_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// BusyboxSpec defines the desired state of Busybox\ntype BusyboxSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of Busybox instances\n\t// +kubebuilder:default=1\n\t// +kubebuilder:validation:Minimum=0\n\t// +optional\n\tSize *int32 `json:\"size,omitempty\"`\n}\n\n// BusyboxStatus defines the observed state of Busybox\ntype BusyboxStatus struct {\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Busybox resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Busybox is the Schema for the busyboxes API\ntype Busybox struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Busybox\n\t// +required\n\tSpec BusyboxSpec `json:\"spec\"`\n\n\t// status defines the observed state of Busybox\n\t// +optional\n\tStatus BusyboxStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// BusyboxList contains a list of Busybox\ntype BusyboxList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Busybox `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Busybox{}, &BusyboxList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1alpha1/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha1 contains API Schema definitions for the example.com v1alpha1 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v1alpha1\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1alpha1/memcached_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// MemcachedSpec defines the desired state of Memcached\ntype MemcachedSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// size defines the number of Memcached instances\n\t// +kubebuilder:default=1\n\t// +kubebuilder:validation:Minimum=0\n\t// +optional\n\tSize *int32 `json:\"size,omitempty\"`\n\n\t// containerPort defines the port that will be used to init the container with the image\n\t// +required\n\tContainerPort int32 `json:\"containerPort\"`\n}\n\n// MemcachedStatus defines the observed state of Memcached\ntype MemcachedStatus struct {\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Memcached resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Memcached is the Schema for the memcacheds API\ntype Memcached struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Memcached\n\t// +required\n\tSpec MemcachedSpec `json:\"spec\"`\n\n\t// status defines the observed state of Memcached\n\t// +optional\n\tStatus MemcachedStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// MemcachedList contains a list of Memcached\ntype MemcachedList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Memcached `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Memcached{}, &MemcachedList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Busybox) DeepCopyInto(out *Busybox) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Busybox.\nfunc (in *Busybox) DeepCopy() *Busybox {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Busybox)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Busybox) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxList) DeepCopyInto(out *BusyboxList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Busybox, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxList.\nfunc (in *BusyboxList) DeepCopy() *BusyboxList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *BusyboxList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) {\n\t*out = *in\n\tif in.Size != nil {\n\t\tin, out := &in.Size, &out.Size\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec.\nfunc (in *BusyboxSpec) DeepCopy() *BusyboxSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *BusyboxStatus) DeepCopyInto(out *BusyboxStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxStatus.\nfunc (in *BusyboxStatus) DeepCopy() *BusyboxStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BusyboxStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Memcached) DeepCopyInto(out *Memcached) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached.\nfunc (in *Memcached) DeepCopy() *Memcached {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Memcached)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Memcached) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedList) DeepCopyInto(out *MemcachedList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Memcached, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList.\nfunc (in *MemcachedList) DeepCopy() *MemcachedList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MemcachedList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) {\n\t*out = *in\n\tif in.Size != nil {\n\t\tin, out := &in.Size, &out.Size\n\t\t*out = new(int32)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec.\nfunc (in *MemcachedSpec) DeepCopy() *MemcachedSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus.\nfunc (in *MemcachedStatus) DeepCopy() *MemcachedStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MemcachedStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v2/groupversion_info.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v2 contains API Schema definitions for the example.com v2 API group.\n// +kubebuilder:object:generate=true\n// +groupName=example.com.testproject.org\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects.\n\t// This name is used by applyconfiguration generators (e.g. controller-gen).\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"example.com.testproject.org\", Version: \"v2\"}\n\n\t// GroupVersion is an alias for SchemeGroupVersion, for backward compatibility.\n\tGroupVersion = SchemeGroupVersion\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme.\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v2/wordpress_conversion.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\t\"log\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/conversion\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n)\n\n// ConvertTo converts this Wordpress (v2) to the Hub version (v1).\nfunc (src *Wordpress) ConvertTo(dstRaw conversion.Hub) error {\n\tdst := dstRaw.(*examplecomv1.Wordpress)\n\tlog.Printf(\"ConvertTo: Converting Wordpress from Spoke version v2 to Hub version v1;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v2 to v1\n\t// Example: Copying Spec fields\n\t// dst.Spec.Size = src.Spec.Replicas\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n\n// ConvertFrom converts the Hub version (v1) to this Wordpress (v2).\nfunc (dst *Wordpress) ConvertFrom(srcRaw conversion.Hub) error {\n\tsrc := srcRaw.(*examplecomv1.Wordpress)\n\tlog.Printf(\"ConvertFrom: Converting Wordpress from Hub version v1 to Spoke version v2;\"+\n\t\t\"source: %s/%s, target: %s/%s\", src.Namespace, src.Name, dst.Namespace, dst.Name)\n\n\t// TODO(user): Implement conversion logic from v1 to v2\n\t// Example: Copying Spec fields\n\t// dst.Spec.Replicas = src.Spec.Size\n\n\t// Copy ObjectMeta to preserve name, namespace, labels, etc.\n\tdst.ObjectMeta = src.ObjectMeta\n\n\treturn nil\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v2/wordpress_types.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// WordpressSpec defines the desired state of Wordpress\ntype WordpressSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\t// The following markers will use OpenAPI v3 schema to validate the value\n\t// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html\n\n\t// foo is an example field of Wordpress. Edit wordpress_types.go to remove/update\n\t// +optional\n\tFoo *string `json:\"foo,omitempty\"`\n}\n\n// WordpressStatus defines the observed state of Wordpress.\ntype WordpressStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"make\" to regenerate code after modifying this file\n\n\t// For Kubernetes API conventions, see:\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties\n\n\t// conditions represent the current state of the Wordpress resource.\n\t// Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\t//\n\t// Standard condition types include:\n\t// - \"Available\": the resource is fully functional\n\t// - \"Progressing\": the resource is being created or updated\n\t// - \"Degraded\": the resource failed to reach or maintain its desired state\n\t//\n\t// The status of each condition is one of True, False, or Unknown.\n\t// +listType=map\n\t// +listMapKey=type\n\t// +optional\n\tConditions []metav1.Condition `json:\"conditions,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// Wordpress is the Schema for the wordpresses API\ntype Wordpress struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\n\t// metadata is a standard object metadata\n\t// +optional\n\tmetav1.ObjectMeta `json:\"metadata,omitzero\"`\n\n\t// spec defines the desired state of Wordpress\n\t// +required\n\tSpec WordpressSpec `json:\"spec\"`\n\n\t// status defines the observed state of Wordpress\n\t// +optional\n\tStatus WordpressStatus `json:\"status,omitzero\"`\n}\n\n// +kubebuilder:object:root=true\n\n// WordpressList contains a list of Wordpress\ntype WordpressList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitzero\"`\n\tItems           []Wordpress `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&Wordpress{}, &WordpressList{})\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/api/v2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v2\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Wordpress) DeepCopyInto(out *Wordpress) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Wordpress.\nfunc (in *Wordpress) DeepCopy() *Wordpress {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Wordpress)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Wordpress) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressList) DeepCopyInto(out *WordpressList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Wordpress, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressList.\nfunc (in *WordpressList) DeepCopy() *WordpressList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *WordpressList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressSpec) DeepCopyInto(out *WordpressSpec) {\n\t*out = *in\n\tif in.Foo != nil {\n\t\tin, out := &in.Foo, &out.Foo\n\t\t*out = new(string)\n\t\t**out = **in\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressSpec.\nfunc (in *WordpressSpec) DeepCopy() *WordpressSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *WordpressStatus) DeepCopyInto(out *WordpressStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WordpressStatus.\nfunc (in *WordpressStatus) DeepCopy() *WordpressStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(WordpressStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/cmd/main.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t// to ensure that exec-entrypoint and run can make use of them.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/healthz\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics/filters\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n\texamplecomv2 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v2\"\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/controller\"\n\twebhookv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1\"\n\twebhookv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/internal/webhook/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(examplecomv1alpha1.AddToScheme(scheme))\n\tutilruntime.Must(examplecomv1.AddToScheme(scheme))\n\tutilruntime.Must(examplecomv2.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\n// getWatchNamespace returns the namespace(s) the manager should watch for changes.\n// It reads the value from the WATCH_NAMESPACE environment variable.\n// - If WATCH_NAMESPACE is not set, an error is returned\n// - If WATCH_NAMESPACE contains a single namespace, the manager watches that namespace\n// - If WATCH_NAMESPACE contains comma-separated namespaces, the manager watches those namespaces\nfunc getWatchNamespace() (string, error) {\n\twatchNamespaceEnvVar := \"WATCH_NAMESPACE\"\n\tns, found := os.LookupEnv(watchNamespaceEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"%s must be set\", watchNamespaceEnvVar)\n\t}\n\treturn ns, nil\n}\n\n// setupCacheNamespaces configures the cache to watch specific namespace(s).\n// It supports both single namespace (\"ns1\") and multi-namespace (\"ns1,ns2,ns3\") formats.\nfunc setupCacheNamespaces(namespaces string) cache.Options {\n\tdefaultNamespaces := make(map[string]cache.Config)\n\tfor ns := range strings.SplitSeq(namespaces, \",\") {\n\t\tdefaultNamespaces[strings.TrimSpace(ns)] = cache.Config{}\n\t}\n\treturn cache.Options{\n\t\tDefaultNamespaces: defaultNamespaces,\n\t}\n}\n\n// nolint:gocyclo\nfunc main() {\n\tvar metricsAddr string\n\tvar metricsCertPath, metricsCertName, metricsCertKey string\n\tvar webhookCertPath, webhookCertName, webhookCertKey string\n\tvar enableLeaderElection bool\n\tvar probeAddr string\n\tvar secureMetrics bool\n\tvar enableHTTP2 bool\n\tvar tlsOpts []func(*tls.Config)\n\tflag.StringVar(&metricsAddr, \"metrics-bind-address\", \"0\", \"The address the metrics endpoint binds to. \"+\n\t\t\"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.\")\n\tflag.StringVar(&probeAddr, \"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"leader-elect\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.BoolVar(&secureMetrics, \"metrics-secure\", true,\n\t\t\"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.\")\n\tflag.StringVar(&webhookCertPath, \"webhook-cert-path\", \"\", \"The directory that contains the webhook certificate.\")\n\tflag.StringVar(&webhookCertName, \"webhook-cert-name\", \"tls.crt\", \"The name of the webhook certificate file.\")\n\tflag.StringVar(&webhookCertKey, \"webhook-cert-key\", \"tls.key\", \"The name of the webhook key file.\")\n\tflag.StringVar(&metricsCertPath, \"metrics-cert-path\", \"\",\n\t\t\"The directory that contains the metrics server certificate.\")\n\tflag.StringVar(&metricsCertName, \"metrics-cert-name\", \"tls.crt\", \"The name of the metrics server certificate file.\")\n\tflag.StringVar(&metricsCertKey, \"metrics-cert-key\", \"tls.key\", \"The name of the metrics server key file.\")\n\tflag.BoolVar(&enableHTTP2, \"enable-http2\", false,\n\t\t\"If set, HTTP/2 will be enabled for the metrics and webhook servers\")\n\topts := zap.Options{\n\t\tDevelopment: true,\n\t}\n\topts.BindFlags(flag.CommandLine)\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))\n\n\t// if the enable-http2 flag is false (the default), http/2 should be disabled\n\t// due to its vulnerabilities. More specifically, disabling http/2 will\n\t// prevent from being vulnerable to the HTTP/2 Stream Cancellation and\n\t// Rapid Reset CVEs. For more information see:\n\t// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3\n\t// - https://github.com/advisories/GHSA-4374-p667-p6c8\n\tdisableHTTP2 := func(c *tls.Config) {\n\t\tsetupLog.Info(\"Disabling HTTP/2\")\n\t\tc.NextProtos = []string{\"http/1.1\"}\n\t}\n\n\tif !enableHTTP2 {\n\t\ttlsOpts = append(tlsOpts, disableHTTP2)\n\t}\n\n\t// Initial webhook TLS options\n\twebhookTLSOpts := tlsOpts\n\twebhookServerOptions := webhook.Options{\n\t\tTLSOpts: webhookTLSOpts,\n\t}\n\n\tif len(webhookCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing webhook certificate watcher using provided certificates\",\n\t\t\t\"webhook-cert-path\", webhookCertPath, \"webhook-cert-name\", webhookCertName, \"webhook-cert-key\", webhookCertKey)\n\n\t\twebhookServerOptions.CertDir = webhookCertPath\n\t\twebhookServerOptions.CertName = webhookCertName\n\t\twebhookServerOptions.KeyName = webhookCertKey\n\t}\n\n\twebhookServer := webhook.NewServer(webhookServerOptions)\n\n\t// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.\n\t// More info:\n\t// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/server\n\t// - https://book.kubebuilder.io/reference/metrics.html\n\tmetricsServerOptions := metricsserver.Options{\n\t\tBindAddress:   metricsAddr,\n\t\tSecureServing: secureMetrics,\n\t\tTLSOpts:       tlsOpts,\n\t}\n\n\tif secureMetrics {\n\t\t// FilterProvider is used to protect the metrics endpoint with authn/authz.\n\t\t// These configurations ensure that only authorized users and service accounts\n\t\t// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:\n\t\t// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/metrics/filters#WithAuthenticationAndAuthorization\n\t\tmetricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization\n\t}\n\n\t// If the certificate is not specified, controller-runtime will automatically\n\t// generate self-signed certificates for the metrics server. While convenient for development and testing,\n\t// this setup is not recommended for production.\n\t//\n\t// TODO(user): If you enable certManager, uncomment the following lines:\n\t// - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates\n\t// managed by cert-manager for the metrics server.\n\t// - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification.\n\tif len(metricsCertPath) > 0 {\n\t\tsetupLog.Info(\"Initializing metrics certificate watcher using provided certificates\",\n\t\t\t\"metrics-cert-path\", metricsCertPath, \"metrics-cert-name\", metricsCertName, \"metrics-cert-key\", metricsCertKey)\n\n\t\tmetricsServerOptions.CertDir = metricsCertPath\n\t\tmetricsServerOptions.CertName = metricsCertName\n\t\tmetricsServerOptions.KeyName = metricsCertKey\n\t}\n\n\t// Get the namespace(s) for namespace-scoped mode from WATCH_NAMESPACE environment variable.\n\t// The manager will only watch and manage resources in the specified namespace(s).\n\twatchNamespace, err := getWatchNamespace()\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Unable to get WATCH_NAMESPACE, \"+\n\t\t\t\"the manager will watch and manage resources in all namespaces\")\n\t\tos.Exit(1)\n\t}\n\n\t// Configure manager options for namespace-scoped mode\n\tmgrOptions := ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tMetrics:                metricsServerOptions,\n\t\tWebhookServer:          webhookServer,\n\t\tHealthProbeBindAddress: probeAddr,\n\t\tLeaderElection:         enableLeaderElection,\n\t\tLeaderElectionID:       \"a13ae5d8.testproject.org\",\n\t\t// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily\n\t\t// when the Manager ends. This requires the binary to immediately end when the\n\t\t// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly\n\t\t// speeds up voluntary leader transitions as the new leader don't have to wait\n\t\t// LeaseDuration time first.\n\t\t//\n\t\t// In the default scaffold provided, the program ends immediately after\n\t\t// the manager stops, so would be fine to enable this option. However,\n\t\t// if you are doing or is intended to do any operation such as perform cleanups\n\t\t// after the manager stops then its usage might be unsafe.\n\t\t// LeaderElectionReleaseOnCancel: true,\n\t}\n\n\t// Configure cache to watch namespace(s) specified in WATCH_NAMESPACE\n\tmgrOptions.Cache = setupCacheNamespaces(watchNamespace)\n\tsetupLog.Info(\"Watching namespace(s)\", \"namespaces\", watchNamespace)\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOptions)\n\tif err != nil {\n\t\tsetupLog.Error(err, \"Failed to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := (&controller.MemcachedReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\tRecorder: mgr.GetEventRecorder(\"memcached-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Memcached\")\n\t\tos.Exit(1)\n\t}\n\tif err := (&controller.BusyboxReconciler{\n\t\tClient:   mgr.GetClient(),\n\t\tScheme:   mgr.GetScheme(),\n\t\tRecorder: mgr.GetEventRecorder(\"busybox-controller\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Busybox\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1alpha1.SetupMemcachedWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Memcached\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\tif err := (&controller.WordpressReconciler{\n\t\tClient: mgr.GetClient(),\n\t\tScheme: mgr.GetScheme(),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"Failed to create controller\", \"controller\", \"Wordpress\")\n\t\tos.Exit(1)\n\t}\n\t// nolint:goconst\n\tif os.Getenv(\"ENABLE_WEBHOOKS\") != \"false\" {\n\t\tif err := webhookv1.SetupWordpressWebhookWithManager(mgr); err != nil {\n\t\t\tsetupLog.Error(err, \"Failed to create webhook\", \"webhook\", \"Wordpress\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up health check\")\n\t\tos.Exit(1)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\tsetupLog.Error(err, \"Failed to set up ready check\")\n\t\tos.Exit(1)\n\t}\n\n\tsetupLog.Info(\"Starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"Failed to run manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/certmanager/certificate-metrics.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a metrics certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: metrics-certs  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  dnsNames:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: metrics-server-cert\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/certmanager/certificate-webhook.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n  # replacements in the config/default/kustomization.yaml file.\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/certmanager/issuer.yaml",
    "content": "# The following manifest contains a self-signed issuer CR.\n# More information can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/certmanager/kustomization.yaml",
    "content": "resources:\n- issuer.yaml\n- certificate-webhook.yaml\n- certificate-metrics.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref substitution\nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_busyboxes.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: busyboxes.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Busybox\n    listKind: BusyboxList\n    plural: busyboxes\n    singular: busybox\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Busybox is the Schema for the busyboxes API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Busybox\n            properties:\n              size:\n                default: 1\n                description: size defines the number of Busybox instances\n                format: int32\n                minimum: 0\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Busybox\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Busybox resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_memcacheds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              containerPort:\n                description: containerPort defines the port that will be used to init\n                  the container with the image\n                format: int32\n                type: integer\n              size:\n                default: 1\n                description: size defines the number of Memcached instances\n                format: int32\n                minimum: 0\n                type: integer\n            required:\n            - containerPort\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/bases/example.com.testproject.org_wordpresses.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: wordpresses.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Wordpress\n    listKind: WordpressList\n    plural: wordpresses\n    singular: wordpress\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/example.com.testproject.org_memcacheds.yaml\n- bases/example.com.testproject.org_busyboxes.yaml\n- bases/example.com.testproject.org_wordpresses.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatches:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n- path: patches/webhook_in_wordpresses.yaml\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [WEBHOOK] To enable webhook, uncomment the following section\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    version: v1\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhook/clientConfig/service/name\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/crd/patches/webhook_in_wordpresses.yaml",
    "content": "# The following patch enables a conversion webhook for the CRD\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: wordpresses.example.com.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          namespace: system\n          name: webhook-service\n          path: /convert\n      conversionReviewVersions:\n      - v1\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/default/cert_metrics_manager_patch.yaml",
    "content": "# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.\n\n# Add the volumeMount for the metrics-server certs\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-metrics-server/metrics-certs\n    name: metrics-certs\n    readOnly: true\n\n# Add the --metrics-cert-path argument for the metrics server\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs\n\n# Add the metrics-server certs volume configuration\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: metrics-certs\n    secret:\n      secretName: metrics-server-cert\n      optional: false\n      items:\n        - key: ca.crt\n          path: ca.crt\n        - key: tls.crt\n          path: tls.crt\n        - key: tls.key\n          path: tls.key\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: project-v4-with-plugins-system\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: project-v4-with-plugins-\n\n# Labels to add to all resources and selectors.\n#labels:\n#- includeSelectors: true\n#  pairs:\n#    someName: someValue\n\nresources:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n#- ../prometheus\n# [METRICS] Expose the controller manager metrics service.\n- metrics_service.yaml\n# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.\n# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.\n# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will\n# be able to communicate with the Webhook Server.\n#- ../network-policy\n\n# Uncomment the patches line if you enable Metrics\npatches:\n# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.\n# More info: https://book.kubebuilder.io/reference/metrics\n- path: manager_metrics_patch.yaml\n  target:\n    kind: Deployment\n\n# Uncomment the patches line if you enable Metrics and CertManager\n# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.\n# This patch will protect the metrics with certManager self-signed certs.\n#- path: cert_metrics_manager_patch.yaml\n#  target:\n#    kind: Deployment\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n- path: manager_webhook_patch.yaml\n  target:\n    kind: Deployment\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n# Uncomment the following replacements to add the cert-manager CA injection annotations\nreplacements:\n# - source: # Uncomment the following block to enable certificates for metrics\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.name\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 0\n#         create: true\n\n# - source:\n#     kind: Service\n#     version: v1\n#     name: controller-manager-metrics-service\n#     fieldPath: metadata.namespace\n#   targets:\n#     - select:\n#         kind: Certificate\n#         group: cert-manager.io\n#         version: v1\n#         name: metrics-certs\n#       fieldPaths:\n#         - spec.dnsNames.0\n#         - spec.dnsNames.1\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor\n#         kind: ServiceMonitor\n#         group: monitoring.coreos.com\n#         version: v1\n#         name: controller-manager-metrics-monitor\n#       fieldPaths:\n#         - spec.endpoints.0.tlsConfig.serverName\n#       options:\n#         delimiter: '.'\n#         index: 1\n#         create: true\n\n - source: # Uncomment the following block if you have any webhook\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.name # Name of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 0\n         create: true\n - source:\n     kind: Service\n     version: v1\n     name: webhook-service\n     fieldPath: .metadata.namespace # Namespace of the service\n   targets:\n     - select:\n         kind: Certificate\n         group: cert-manager.io\n         version: v1\n         name: serving-cert\n       fieldPaths:\n         - .spec.dnsNames.0\n         - .spec.dnsNames.1\n       options:\n         delimiter: '.'\n         index: 1\n         create: true\n\n - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert # This name should match the one in certificate.yaml\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets:\n     - select:\n         kind: ValidatingWebhookConfiguration\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n\n# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.namespace # Namespace of the certificate CR\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 0\n#         create: true\n# - source:\n#     kind: Certificate\n#     group: cert-manager.io\n#     version: v1\n#     name: serving-cert\n#     fieldPath: .metadata.name\n#   targets:\n#     - select:\n#         kind: MutatingWebhookConfiguration\n#       fieldPaths:\n#         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n#       options:\n#         delimiter: '/'\n#         index: 1\n#         create: true\n\n - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.namespace # Namespace of the certificate CR\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: wordpresses.example.com.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 0\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionns\n - source:\n     kind: Certificate\n     group: cert-manager.io\n     version: v1\n     name: serving-cert\n     fieldPath: .metadata.name\n   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.\n     - select:\n         kind: CustomResourceDefinition\n         name: wordpresses.example.com.testproject.org\n       fieldPaths:\n         - .metadata.annotations.[cert-manager.io/inject-ca-from]\n       options:\n         delimiter: '/'\n         index: 1\n         create: true\n# +kubebuilder:scaffold:crdkustomizecainjectionname\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/default/manager_metrics_patch.yaml",
    "content": "# This patch adds the args to allow exposing the metrics endpoint using HTTPS\n- op: add\n  path: /spec/template/spec/containers/0/args/0\n  value: --metrics-bind-address=:8443\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/default/manager_webhook_patch.yaml",
    "content": "# This patch ensures the webhook certificates are properly mounted in the manager container.\n# It configures the necessary arguments, volumes, volume mounts, and container ports.\n\n# Add the --webhook-cert-path argument for configuring the webhook certificate path\n- op: add\n  path: /spec/template/spec/containers/0/args/-\n  value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n\n# Add the volumeMount for the webhook certificates\n- op: add\n  path: /spec/template/spec/containers/0/volumeMounts/-\n  value:\n    mountPath: /tmp/k8s-webhook-server/serving-certs\n    name: webhook-certs\n    readOnly: true\n\n# Add the port configuration for the webhook server\n- op: add\n  path: /spec/template/spec/containers/0/ports/-\n  value:\n    containerPort: 9443\n    name: webhook-server\n    protocol: TCP\n\n# Add the volume configuration for the webhook certificates\n- op: add\n  path: /spec/template/spec/volumes/-\n  value:\n    name: webhook-certs\n    secret:\n      secretName: webhook-server-cert\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/default/metrics_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-service\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: controller\n  newTag: latest\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\nspec:\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-with-plugins\n  replicas: 1\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        control-plane: controller-manager\n        app.kubernetes.io/name: project-v4-with-plugins\n    spec:\n      # TODO(user): Uncomment the following code to configure the nodeAffinity expression\n      # according to the platforms which are supported by your solution.\n      # It is considered best practice to support multiple architectures. You can\n      # build your manager image using the makefile target docker-buildx.\n      # affinity:\n      #   nodeAffinity:\n      #     requiredDuringSchedulingIgnoredDuringExecution:\n      #       nodeSelectorTerms:\n      #         - matchExpressions:\n      #           - key: kubernetes.io/arch\n      #             operator: In\n      #             values:\n      #               - amd64\n      #               - arm64\n      #               - ppc64le\n      #               - s390x\n      #           - key: kubernetes.io/os\n      #             operator: In\n      #             values:\n      #               - linux\n      securityContext:\n        # Projects are configured by default to adhere to the \"restricted\" Pod Security Standards.\n        # This ensures that deployments meet the highest security requirements for Kubernetes.\n        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      containers:\n      - command:\n        - /manager\n        args:\n          - --leader-elect\n          - --health-probe-bind-address=:8081\n        image: controller:latest\n        name: manager\n        env:\n        - name: BUSYBOX_IMAGE\n          value: busybox:1.36.1\n        - name: MEMCACHED_IMAGE\n          value: memcached:1.6.26-alpine3.19\n        - name: WATCH_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        ports: []\n        securityContext:\n          readOnlyRootFilesystem: true\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - \"ALL\"\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        # TODO(user): Configure the resources accordingly based on the project requirements.\n        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        volumeMounts: []\n      volumes: []\n      serviceAccountName: controller-manager\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/network-policy/allow-metrics-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic\n# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those\n# namespaces are able to gather data from the metrics endpoint.\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-metrics-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-with-plugins\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label metrics: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            metrics: enabled  # Only from namespaces with this label\n      ports:\n        - port: 8443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/network-policy/allow-webhook-traffic.yaml",
    "content": "# This NetworkPolicy allows ingress traffic to your webhook server running\n# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks\n# will only work when applied in namespaces labeled with 'webhook: enabled'\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: allow-webhook-traffic\n  namespace: system\nspec:\n  podSelector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-with-plugins\n  policyTypes:\n    - Ingress\n  ingress:\n    # This allows ingress traffic from any namespace with the label webhook: enabled\n    - from:\n      - namespaceSelector:\n          matchLabels:\n            webhook: enabled # Only from namespaces with this label\n      ports:\n        - port: 443\n          protocol: TCP\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/network-policy/kustomization.yaml",
    "content": "resources:\n- allow-webhook-traffic.yaml\n- allow-metrics-traffic.yaml\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\n# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus\n# to securely reference certificates created and managed by cert-manager.\n# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml\n# to mount the \"metrics-server-cert\" secret in the Manager Deployment.\n#patches:\n#  - path: monitor_tls_patch.yaml\n#    target:\n#      kind: ServiceMonitor\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/prometheus/monitor.yaml",
    "content": "# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - path: /metrics\n      port: https # Ensure this is the name of the port that exposes HTTPS metrics\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables\n        # certificate verification, exposing the system to potential man-in-the-middle attacks.\n        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.\n        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,\n        # which securely references the certificate from the 'metrics-server-cert' secret.\n        insecureSkipVerify: true\n  selector:\n    matchLabels:\n      control-plane: controller-manager\n      app.kubernetes.io/name: project-v4-with-plugins\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/prometheus/monitor_tls_patch.yaml",
    "content": "# Patch for Prometheus ServiceMonitor to enable secure TLS configuration\n# using certificates managed by cert-manager\n- op: replace\n  path: /spec/endpoints/0/tlsConfig\n  value:\n    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize\n    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc\n    insecureSkipVerify: false\n    ca:\n      secret:\n        name: metrics-server-cert\n        key: ca.crt\n    cert:\n      secret:\n        name: metrics-server-cert\n        key: tls.crt\n    keySecret:\n      name: metrics-server-cert\n      key: tls.key\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/busybox_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: busybox-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/busybox_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: busybox-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/busybox_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: busybox-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/kustomization.yaml",
    "content": "resources:\n# All RBAC will be applied under this service account in\n# the deployment namespace. You may comment out this resource\n# if your manager will use a service account that exists at\n# runtime. Be sure to update RoleBinding and ClusterRoleBinding\n# subjects if changing service account names.\n- service_account.yaml\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# The following RBAC configurations are used to protect\n# the metrics endpoint with authn/authz. These configurations\n# ensure that only authorized users and service accounts\n# can access the metrics endpoint. Comment the following\n# permissions if you want to disable this protection.\n# More info: https://book.kubebuilder.io/reference/metrics.html\n- metrics_auth_role.yaml\n- metrics_auth_role_binding.yaml\n- metrics_reader_role.yaml\n# For each CRD, \"Admin\", \"Editor\" and \"Viewer\" roles are scaffolded by\n# default, aiding admins in cluster management. Those roles are\n# not used by the project-v4-with-plugins itself. You can comment the following lines\n# if you do not want those helpers be installed with your Project.\n- wordpress_admin_role.yaml\n- wordpress_editor_role.yaml\n- wordpress_viewer_role.yaml\n- busybox_admin_role.yaml\n- busybox_editor_role.yaml\n- busybox_viewer_role.yaml\n- memcached_admin_role.yaml\n- memcached_editor_role.yaml\n- memcached_viewer_role.yaml\n\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/memcached_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/memcached_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/memcached_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/metrics_auth_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/metrics_auth_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/metrics_reader_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs:\n  - \"/metrics\"\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: manager-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  - memcacheds\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/finalizers\n  - memcacheds/finalizers\n  - wordpresses/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  - memcacheds/status\n  - wordpresses/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/service_account.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/wordpress_admin_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants full permissions ('*') over example.com.testproject.org.\n# This role is intended for users authorized to modify roles and bindings within the cluster,\n# enabling them to delegate specific permissions to other users or groups as needed.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-admin-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/wordpress_editor_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants permissions to create, update, and delete resources within the example.com.testproject.org.\n# This role is intended for users who need to manage these resources\n# but should not control RBAC or manage permissions for others.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-editor-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/rbac/wordpress_viewer_role.yaml",
    "content": "# This rule is not used by the project project-v4-with-plugins itself.\n# It is provided to allow the cluster admin to help manage permissions for users.\n#\n# Grants read-only access to example.com.testproject.org resources.\n# This role is intended for users who need visibility into these resources\n# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-viewer-role\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/samples/example.com_v1_wordpress.yaml",
    "content": "apiVersion: example.com.testproject.org/v1\nkind: Wordpress\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/samples/example.com_v1alpha1_busybox.yaml",
    "content": "apiVersion: example.com.testproject.org/v1alpha1\nkind: Busybox\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: busybox-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/samples/example.com_v1alpha1_memcached.yaml",
    "content": "apiVersion: example.com.testproject.org/v1alpha1\nkind: Memcached\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: memcached-sample\nspec:\n  # TODO(user): edit the following value to ensure the number\n  # of Pods/Instances your Operand must have on cluster\n  size: 1\n\n  # TODO(user): edit the following value to ensure the container has the right port to be initialized\n  containerPort: 11211\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/samples/example.com_v2_wordpress.yaml",
    "content": "apiVersion: example.com.testproject.org/v2\nkind: Wordpress\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: wordpress-sample\nspec:\n  # TODO(user): Add fields here\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/samples/kustomization.yaml",
    "content": "## Append samples of your project ##\nresources:\n- example.com_v1alpha1_memcached.yaml\n- example.com_v1alpha1_busybox.yaml\n- example.com_v1_wordpress.yaml\n- example.com_v2_wordpress.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/webhook/manifests.yaml",
    "content": "---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: webhook-service\n      namespace: system\n      path: /validate-example-com-testproject-org-v1alpha1-memcached\n  failurePolicy: Fail\n  name: vmemcached-v1alpha1.kb.io\n  rules:\n  - apiGroups:\n    - example.com.testproject.org\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - memcacheds\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/config/webhook/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: project-v4-with-plugins\n    app.kubernetes.io/managed-by: kustomize\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      protocol: TCP\n      targetPort: 9443\n  selector:\n    control-plane: controller-manager\n    app.kubernetes.io/name: project-v4-with-plugins\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/.helmignore",
    "content": "# Patterns to ignore when building Helm packages.\n# Operating system files\n.DS_Store\n\n# Version control directories\n.git/\n.gitignore\n.bzr/\n.hg/\n.hgignore\n.svn/\n\n# Backup and temporary files\n*.swp\n*.tmp\n*.bak\n*.orig\n*~\n\n# IDE and editor-related files\n.idea/\n.vscode/\n\n# Helm chart artifacts\ndist/chart/*.tgz\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/Chart.yaml",
    "content": "apiVersion: v2\nname: project-v4-with-plugins\ndescription: A Helm chart to distribute project-v4-with-plugins\ntype: application\n\nversion: 0.1.0\nappVersion: \"0.1.0\"\n\nkeywords:\n  - kubernetes\n  - operator\n\nannotations:\n  kubebuilder.io/generated-by: kubebuilder\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/NOTES.txt",
    "content": "Thank you for installing {{ .Chart.Name }}.\n\nYour release is named {{ .Release.Name }}.\n\nThe controller and CRDs have been installed in namespace {{ .Release.Namespace }}.\n\nTo verify the installation:\n\n  kubectl get pods -n {{ .Release.Namespace }}\n  kubectl get customresourcedefinitions\n\nTo learn more about the release, try:\n\n  $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}\n  $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"project-v4-with-plugins.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"project-v4-with-plugins.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nNamespace for generated references.\nAlways uses the Helm release namespace.\n*/}}\n{{- define \"project-v4-with-plugins.namespaceName\" -}}\n{{- .Release.Namespace }}\n{{- end }}\n\n{{/*\nResource name with proper truncation for Kubernetes 63-character limit.\nTakes a dict with:\n  - .suffix: Resource name suffix (e.g., \"metrics\", \"webhook\")\n  - .context: Template context (root context with .Values, .Release, etc.)\nDynamically calculates safe truncation to ensure total name length <= 63 chars.\n*/}}\n{{- define \"project-v4-with-plugins.resourceName\" -}}\n{{- $fullname := include \"project-v4-with-plugins.fullname\" .context }}\n{{- $suffix := .suffix }}\n{{- $maxLen := sub 62 (len $suffix) | int }}\n{{- if gt (len $fullname) $maxLen }}\n{{- printf \"%s-%s\" (trunc $maxLen $fullname | trimSuffix \"-\") $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" $fullname $suffix | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/cert-manager/metrics-certs.yaml",
    "content": "{{- if and .Values.certManager.enable .Values.metrics.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"metrics-certs\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ include \"project-v4-with-plugins.namespaceName\" $ }}.svc\n  - {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ include \"project-v4-with-plugins.namespaceName\" $ }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: metrics-server-cert\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/cert-manager/selfsigned-issuer.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  selfSigned: {}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/cert-manager/serving-cert.yaml",
    "content": "{{- if .Values.certManager.enable }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  dnsNames:\n  - {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n  - {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"selfsigned-issuer\" \"context\" $) }}\n  secretName: webhook-server-cert\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/crd/busyboxes.example.com.testproject.org.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: busyboxes.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Busybox\n    listKind: BusyboxList\n    plural: busyboxes\n    singular: busybox\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Busybox is the Schema for the busyboxes API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Busybox\n            properties:\n              size:\n                default: 1\n                description: size defines the number of Busybox instances\n                format: int32\n                minimum: 0\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Busybox\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Busybox resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/crd/memcacheds.example.com.testproject.org.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              containerPort:\n                description: containerPort defines the port that will be used to init\n                  the container with the image\n                format: int32\n                type: integer\n              size:\n                default: 1\n                description: size defines the number of Memcached instances\n                format: int32\n                minimum: 0\n                type: integer\n            required:\n            - containerPort\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/crd/wordpresses.example.com.testproject.org.yaml",
    "content": "{{- if .Values.crd.enable }}\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    {{- if .Values.crd.keep }}\n    \"helm.sh/resource-policy\": keep\n    {{- end }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: wordpresses.example.com.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n          namespace: {{ .Release.Namespace }}\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: example.com.testproject.org\n  names:\n    kind: Wordpress\n    listKind: WordpressList\n    plural: wordpresses\n    singular: wordpress\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/manager/manager.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: {{ .Values.manager.replicas }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n        helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n        app.kubernetes.io/managed-by: {{ .Release.Service }}\n        control-plane: controller-manager\n    spec:\n      {{- with .Values.manager.tolerations }}\n      tolerations: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.affinity }}\n      affinity: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      {{- with .Values.manager.nodeSelector }}\n      nodeSelector: {{ toYaml . | nindent 10 }}\n      {{- end }}\n      containers:\n      - args:\n        {{- if .Values.metrics.enable }}\n        - --metrics-bind-address=:{{ .Values.metrics.port }}\n        {{- else }}\n        # Bind to :0 to disable the controller-runtime managed metrics server\n        - --metrics-bind-address=0\n        {{- end }}\n        - --health-probe-bind-address=:8081\n        {{- range .Values.manager.args }}\n        - {{ . }}\n        {{- end }}\n        {{- if .Values.certManager.enable }}\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        {{- end }}\n        command:\n        - /manager\n        env:\n{{- if or .Values.manager.env (and (kindIs \"map\" .Values.manager.envOverrides) (not (empty .Values.manager.envOverrides))) }}\n          {{- if .Values.manager.env }}\n          {{- toYaml .Values.manager.env | nindent 10 }}\n          {{- end }}\n          {{- if kindIs \"map\" .Values.manager.envOverrides }}\n          {{- range $k, $v := .Values.manager.envOverrides }}\n          - name: {{ $k }}\n            value: {{ $v | quote }}\n          {{ end }}\n          {{- end }}\n          {{- else }}\n          []\n          {{- end }}\n        image: \"{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag }}\"\n        imagePullPolicy: {{ .Values.manager.image.pullPolicy }}\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: {{ .Values.webhook.port }}\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          {{- if .Values.manager.resources }}\n          {{- toYaml .Values.manager.resources | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        securityContext:\n          {{- if .Values.manager.securityContext }}\n          {{- toYaml .Values.manager.securityContext | nindent 10 }}\n          {{- else }}\n          {}\n          {{- end }}\n        volumeMounts:\n        {{- if .Values.certManager.enable }}\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n        {{- end }}\n      securityContext:\n        {{- if .Values.manager.podSecurityContext }}\n        {{- toYaml .Values.manager.podSecurityContext | nindent 8 }}\n        {{- else }}\n        {}\n        {{- end }}\n      serviceAccountName: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n      terminationGracePeriodSeconds: 10\n      volumes:\n      {{- if .Values.certManager.enable }}\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n      {{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/metrics/controller-manager-metrics-service.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - name: https\n    port: {{ .Values.metrics.port }}\n    protocol: TCP\n    targetPort: {{ .Values.metrics.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/monitoring/servicemonitor.yaml",
    "content": "{{- if .Values.prometheus.enable }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n    control-plane: controller-manager\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager-metrics-monitor\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  endpoints:\n    - path: /metrics\n      port: https\n      scheme: https\n      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      tlsConfig:\n        {{- if .Values.certManager.enable }}\n        serverName: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager-metrics-service\" \"context\" $) }}.{{ .Release.Namespace }}.svc\n        # Apply secure TLS configuration with cert-manager\n        insecureSkipVerify: false\n        ca:\n          secret:\n            name: metrics-server-cert\n            key: ca.crt\n        cert:\n          secret:\n            name: metrics-server-cert\n            key: tls.crt\n        keySecret:\n          name: metrics-server-cert\n          key: tls.key\n        {{- else }}\n        # Development/Test mode (insecure configuration)\n        insecureSkipVerify: true\n        {{- end }}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n      control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"busybox-admin-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"busybox-editor-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/busybox-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"busybox-viewer-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/controller-manager.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader-election-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/leader-election-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"leader-election-rolebinding\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"leader-election-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/manager-role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  - memcacheds\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/finalizers\n  - memcacheds/finalizers\n  - wordpresses/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  - memcacheds/status\n  - wordpresses/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/manager-rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"manager-rolebinding\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"manager-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"memcached-admin-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"memcached-editor-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/memcached-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"memcached-viewer-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics-auth-role.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics-auth-rolebinding.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"metrics-auth-rolebinding\" \"context\" $) }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"metrics-auth-role\" \"context\" $) }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"controller-manager\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/metrics-reader.yaml",
    "content": "{{- if .Values.metrics.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"metrics-reader\" \"context\" $) }}\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress-admin-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"wordpress-admin-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress-editor-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"wordpress-editor-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/rbac/wordpress-viewer-role.yaml",
    "content": "{{- if .Values.rbacHelpers.enable }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"wordpress-viewer-role\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/webhook/validating-webhook-configuration.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    {{- if .Values.certManager.enable }}\n    cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"serving-cert\" \"context\" $) }}\n    {{- end }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"validating-webhook-configuration\" \"context\" $) }}\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n      namespace: {{ .Release.Namespace }}\n      path: /validate-example-com-testproject-org-v1alpha1-memcached\n  failurePolicy: Fail\n  name: vmemcached-v1alpha1.kb.io\n  rules:\n  - apiGroups:\n    - example.com.testproject.org\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - memcacheds\n  sideEffects: None\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/templates/webhook/webhook-service.yaml",
    "content": "{{- if .Values.webhook.enable }}\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service }}\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace \"+\" \"_\" }}\n    app.kubernetes.io/instance: {{ .Release.Name }}\n  name: {{ include \"project-v4-with-plugins.resourceName\" (dict \"suffix\" \"webhook-service\" \"context\" $) }}\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: {{ .Values.webhook.port }}\n  selector:\n    app.kubernetes.io/name: {{ include \"project-v4-with-plugins.name\" . }}\n    control-plane: controller-manager\n{{- end }}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/chart/values.yaml",
    "content": "## String to partially override chart.fullname template (will maintain the release name)\n##\n# nameOverride: \"\"\n\n## String to fully override chart.fullname template\n##\n# fullnameOverride: \"\"\n\n## Configure the controller manager deployment\n##\nmanager:\n  replicas: 1\n\n  image:\n    repository: controller\n    tag: latest\n    pullPolicy: IfNotPresent\n\n  ## Arguments\n  ##\n  args:\n    - --leader-elect\n\n  ## Environment variables\n  ##\n  env:\n    - name: BUSYBOX_IMAGE\n      value: busybox:1.36.1\n    - name: MEMCACHED_IMAGE\n      value: memcached:1.6.26-alpine3.19\n    - name: WATCH_NAMESPACE\n      valueFrom:\n        fieldRef:\n          fieldPath: metadata.namespace\n\n  ## Env overrides (--set manager.envOverrides.VAR=value)\n  ## Same name in env above: this value takes precedence.\n  ##\n  envOverrides: {}\n\n  ## Image pull secrets\n  ##\n  imagePullSecrets: []\n\n  ## Pod-level security settings\n  ##\n  podSecurityContext:\n    runAsNonRoot: true\n    seccompProfile:\n      type: RuntimeDefault\n\n  ## Container-level security settings\n  ##\n  securityContext:\n    allowPrivilegeEscalation: false\n    capabilities:\n      drop:\n      - ALL\n    readOnlyRootFilesystem: true\n\n  ## Resource limits and requests\n  ##\n  resources:\n    limits:\n      cpu: 500m\n      memory: 128Mi\n    requests:\n      cpu: 10m\n      memory: 64Mi\n\n  ## Manager pod's affinity\n  ##\n  affinity: {}\n\n  ## Manager pod's node selector\n  ##\n  nodeSelector: {}\n\n  ## Manager pod's tolerations\n  ##\n  tolerations: []\n\n## Helper RBAC roles for managing custom resources\n##\nrbacHelpers:\n  # Install convenience admin/editor/viewer roles for CRDs\n  enable: false\n\n## Custom Resource Definitions\n##\ncrd:\n  # Install CRDs with the chart\n  enable: true\n  # Keep CRDs when uninstalling\n  keep: true\n\n## Controller metrics endpoint.\n## Enable to expose /metrics endpoint with RBAC protection.\n##\nmetrics:\n  enable: true\n  # Metrics server port\n  port: 8443\n\n## Cert-manager integration for TLS certificates.\n## Required for webhook certificates and metrics endpoint certificates.\n##\ncertManager:\n  enable: true\n\n## Webhook server configuration\n##\nwebhook:\n  enable: true\n  # Webhook server port\n  port: 9443\n\n## Prometheus ServiceMonitor for metrics scraping.\n## Requires prometheus-operator to be installed in the cluster.\n##\nprometheus:\n  enable: false\n\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/dist/install.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n    control-plane: controller-manager\n  name: project-v4-with-plugins-system\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: busyboxes.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Busybox\n    listKind: BusyboxList\n    plural: busyboxes\n    singular: busybox\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Busybox is the Schema for the busyboxes API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Busybox\n            properties:\n              size:\n                default: 1\n                description: size defines the number of Busybox instances\n                format: int32\n                minimum: 0\n                type: integer\n            type: object\n          status:\n            description: status defines the observed state of Busybox\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Busybox resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: memcacheds.example.com.testproject.org\nspec:\n  group: example.com.testproject.org\n  names:\n    kind: Memcached\n    listKind: MemcachedList\n    plural: memcacheds\n    singular: memcached\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: Memcached is the Schema for the memcacheds API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Memcached\n            properties:\n              containerPort:\n                description: containerPort defines the port that will be used to init\n                  the container with the image\n                format: int32\n                type: integer\n              size:\n                default: 1\n                description: size defines the number of Memcached instances\n                format: int32\n                minimum: 0\n                type: integer\n            required:\n            - containerPort\n            type: object\n          status:\n            description: status defines the observed state of Memcached\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Memcached resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-with-plugins-system/project-v4-with-plugins-serving-cert\n    controller-gen.kubebuilder.io/version: v0.20.1\n  name: wordpresses.example.com.testproject.org\nspec:\n  conversion:\n    strategy: Webhook\n    webhook:\n      clientConfig:\n        service:\n          name: project-v4-with-plugins-webhook-service\n          namespace: project-v4-with-plugins-system\n          path: /convert\n      conversionReviewVersions:\n      - v1\n  group: example.com.testproject.org\n  names:\n    kind: Wordpress\n    listKind: WordpressList\n    plural: wordpresses\n    singular: wordpress\n  scope: Namespaced\n  versions:\n  - name: v1\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - name: v2\n    schema:\n      openAPIV3Schema:\n        description: Wordpress is the Schema for the wordpresses API\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: spec defines the desired state of Wordpress\n            properties:\n              foo:\n                description: foo is an example field of Wordpress. Edit wordpress_types.go\n                  to remove/update\n                type: string\n            type: object\n          status:\n            description: status defines the observed state of Wordpress\n            properties:\n              conditions:\n                description: |-\n                  conditions represent the current state of the Wordpress resource.\n                  Each condition has a unique type and reflects the status of a specific aspect of the resource.\n\n                  Standard condition types include:\n                  - \"Available\": the resource is fully functional\n                  - \"Progressing\": the resource is being created or updated\n                  - \"Degraded\": the resource failed to reach or maintain its desired state\n\n                  The status of each condition is one of True, False, or Unknown.\n                items:\n                  description: Condition contains details for one aspect of the current\n                    state of this API Resource.\n                  properties:\n                    lastTransitionTime:\n                      description: |-\n                        lastTransitionTime is the last time the condition transitioned from one status to another.\n                        This should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: |-\n                        message is a human readable message indicating details about the transition.\n                        This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: |-\n                        observedGeneration represents the .metadata.generation that the condition was set based upon.\n                        For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\n                        with respect to the current state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: |-\n                        reason contains a programmatic identifier indicating the reason for the condition's last transition.\n                        Producers of specific condition types may define expected values and meanings for this field,\n                        and whether the values are considered a guaranteed API.\n                        The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-controller-manager\n  namespace: project-v4-with-plugins-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-busybox-admin-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-busybox-editor-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-busybox-viewer-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-leader-election-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: project-v4-with-plugins-manager-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - events.k8s.io\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes\n  - memcacheds\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/finalizers\n  - memcacheds/finalizers\n  - wordpresses/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - busyboxes/status\n  - memcacheds/status\n  - wordpresses/status\n  verbs:\n  - get\n  - patch\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-memcached-admin-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-memcached-editor-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-memcached-viewer-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - memcacheds/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-wordpress-admin-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - '*'\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-wordpress-editor-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-wordpress-viewer-role\n  namespace: project-v4-with-plugins-system\nrules:\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - example.com.testproject.org\n  resources:\n  - wordpresses/status\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-with-plugins-metrics-auth-role\nrules:\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: project-v4-with-plugins-metrics-reader\nrules:\n- nonResourceURLs:\n  - /metrics\n  verbs:\n  - get\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-leader-election-rolebinding\n  namespace: project-v4-with-plugins-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-v4-with-plugins-leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-with-plugins-controller-manager\n  namespace: project-v4-with-plugins-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-manager-rolebinding\n  namespace: project-v4-with-plugins-system\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: project-v4-with-plugins-manager-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-with-plugins-controller-manager\n  namespace: project-v4-with-plugins-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: project-v4-with-plugins-metrics-auth-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: project-v4-with-plugins-metrics-auth-role\nsubjects:\n- kind: ServiceAccount\n  name: project-v4-with-plugins-controller-manager\n  namespace: project-v4-with-plugins-system\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n    control-plane: controller-manager\n  name: project-v4-with-plugins-controller-manager-metrics-service\n  namespace: project-v4-with-plugins-system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    protocol: TCP\n    targetPort: 8443\n  selector:\n    app.kubernetes.io/name: project-v4-with-plugins\n    control-plane: controller-manager\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-webhook-service\n  namespace: project-v4-with-plugins-system\nspec:\n  ports:\n  - port: 443\n    protocol: TCP\n    targetPort: 9443\n  selector:\n    app.kubernetes.io/name: project-v4-with-plugins\n    control-plane: controller-manager\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n    control-plane: controller-manager\n  name: project-v4-with-plugins-controller-manager\n  namespace: project-v4-with-plugins-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: project-v4-with-plugins\n      control-plane: controller-manager\n  template:\n    metadata:\n      annotations:\n        kubectl.kubernetes.io/default-container: manager\n      labels:\n        app.kubernetes.io/name: project-v4-with-plugins\n        control-plane: controller-manager\n    spec:\n      containers:\n      - args:\n        - --metrics-bind-address=:8443\n        - --leader-elect\n        - --health-probe-bind-address=:8081\n        - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs\n        command:\n        - /manager\n        env:\n        - name: BUSYBOX_IMAGE\n          value: busybox:1.36.1\n        - name: MEMCACHED_IMAGE\n          value: memcached:1.6.26-alpine3.19\n        - name: WATCH_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.namespace\n        image: controller:latest\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 15\n          periodSeconds: 20\n        name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 5\n          periodSeconds: 10\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 10m\n            memory: 64Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          readOnlyRootFilesystem: true\n        volumeMounts:\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: webhook-certs\n          readOnly: true\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: project-v4-with-plugins-controller-manager\n      terminationGracePeriodSeconds: 10\n      volumes:\n      - name: webhook-certs\n        secret:\n          secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-metrics-certs\n  namespace: project-v4-with-plugins-system\nspec:\n  dnsNames:\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc\n  - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-with-plugins-selfsigned-issuer\n  secretName: metrics-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-serving-cert\n  namespace: project-v4-with-plugins-system\nspec:\n  dnsNames:\n  - project-v4-with-plugins-webhook-service.project-v4-with-plugins-system.svc\n  - project-v4-with-plugins-webhook-service.project-v4-with-plugins-system.svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: project-v4-with-plugins-selfsigned-issuer\n  secretName: webhook-server-cert\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  labels:\n    app.kubernetes.io/managed-by: kustomize\n    app.kubernetes.io/name: project-v4-with-plugins\n  name: project-v4-with-plugins-selfsigned-issuer\n  namespace: project-v4-with-plugins-system\nspec:\n  selfSigned: {}\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: project-v4-with-plugins-system/project-v4-with-plugins-serving-cert\n  name: project-v4-with-plugins-validating-webhook-configuration\nwebhooks:\n- admissionReviewVersions:\n  - v1\n  clientConfig:\n    service:\n      name: project-v4-with-plugins-webhook-service\n      namespace: project-v4-with-plugins-system\n      path: /validate-example-com-testproject-org-v1alpha1-memcached\n  failurePolicy: Fail\n  name: vmemcached-v1alpha1.kb.io\n  rules:\n  - apiGroups:\n    - example.com.testproject.org\n    apiVersions:\n    - v1alpha1\n    operations:\n    - CREATE\n    - UPDATE\n    resources:\n    - memcacheds\n  sideEffects: None\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/go.mod",
    "content": "module sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins\n\ngo 1.25.3\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.27.2\n\tgithub.com/onsi/gomega v1.38.2\n\tk8s.io/api v0.35.0\n\tk8s.io/apimachinery v0.35.0\n\tk8s.io/client-go v0.35.0\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n\tsigs.k8s.io/controller-runtime v0.23.3\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/cel-go v0.26.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/client_golang v1.23.2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/spf13/cobra v1.10.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/stoewer/go-strcase v1.3.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.36.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.5.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect\n\tgoogle.golang.org/grpc v1.72.2 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.0 // indirect\n\tk8s.io/apiserver v0.35.0 // indirect\n\tk8s.io/component-base v0.35.0 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/grafana/controller-resources-metrics.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"percent\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 2,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(process_cpu_seconds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}[5m]) * 100\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller CPU Usage\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"bytes\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 4,\n      \"interval\": \"1m\",\n      \"links\": [],\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"list\",\n          \"placement\": \"bottom\"\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"process_resident_memory_bytes{job=\\\"$job\\\", namespace=\\\"$namespace\\\", pod=\\\"$pod\\\"}\",\n          \"format\": \"time_series\",\n          \"interval\": \"\",\n          \"intervalFactor\": 2,\n          \"legendFormat\": \"Pod: {{pod}} | Container: {{container}}\",\n          \"refId\": \"A\",\n          \"step\": 10\n        }\n      ],\n      \"title\": \"Controller Memory Usage\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"observability\",\n          \"value\": \"observability\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Resources-Metrics\",\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/grafana/controller-runtime-metrics.json",
    "content": "{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__requires\": [\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\",\n          \"uid\": \"grafana\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"target\": {\n          \"limit\": 100,\n          \"matchAny\": false,\n          \"tags\": [],\n          \"type\": \"dashboard\"\n        },\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 9,\n      \"panels\": [],\n      \"title\": \"Reconciliation Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 1\n      },\n      \"id\": 24,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"controller_runtime_active_workers{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{controller}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of workers in use\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliations per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 1\n      },\n      \"id\": 7,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Total Reconciliation Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of reconciliation errors per controller\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"cpm\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 1\n      },\n      \"id\": 6,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"editorMode\": \"code\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(controller_runtime_reconcile_errors_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, pod)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{instance}} {{pod}}\",\n          \"range\": true,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Reconciliation Error Count Per Controller\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"collapsed\": false,\n      \"gridPos\": {\n        \"h\": 1,\n        \"w\": 24,\n        \"x\": 0,\n        \"y\": 9\n      },\n      \"id\": 11,\n      \"panels\": [],\n      \"title\": \"Work Queue Metrics\",\n      \"type\": \"row\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 10\n      },\n      \"id\": 22,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"workqueue_depth{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"WorkQueue Depth\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds an item stays in workqueue before being requested\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"normal\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 10\n      },\n      \"id\": 13,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_queue_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds For Items Stay In Queue (before being requested) (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 8,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 10\n      },\n      \"id\": 15,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"pluginVersion\": \"8.4.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_adds_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Add Rate\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How many seconds of work has done that is in progress and hasn't been observed by work_duration.\\nLarge values indicate stuck threads.\\nOne can deduce the number of stuck threads by observing the rate at which this increases.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"percentage\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"orange\",\n                \"value\": 70\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 85\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 3,\n        \"x\": 0,\n        \"y\": 18\n      },\n      \"id\": 23,\n      \"options\": {\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\"lastNotNull\"],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"showThresholdLabels\": false,\n        \"showThresholdMarkers\": true\n      },\n      \"pluginVersion\": \"9.5.3\",\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"rate(workqueue_unfinished_work_seconds{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])\",\n          \"interval\": \"\",\n          \"legendFormat\": \"\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Unfinished Seconds\",\n      \"type\": \"gauge\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"How long in seconds processing an item from workqueue takes.\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"palette-classic\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 10,\n            \"gradientMode\": \"none\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"linear\",\n            \"lineWidth\": 1,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"s\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 11,\n        \"x\": 3,\n        \"y\": 18\n      },\n      \"id\": 19,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [\n            \"max\",\n            \"mean\"\n          ],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.50, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"interval\": \"\",\n          \"legendFormat\": \"P50 {{name}} {{instance}} \",\n          \"refId\": \"A\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.90, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P90 {{name}} {{instance}} \",\n          \"refId\": \"B\"\n        },\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"histogram_quantile(0.99, sum(rate(workqueue_work_duration_seconds_bucket{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name, le))\",\n          \"hide\": false,\n          \"interval\": \"\",\n          \"legendFormat\": \"P99 {{name}} {{instance}} \",\n          \"refId\": \"C\"\n        }\n      ],\n      \"title\": \"Seconds Processing Items From WorkQueue (P50, P90, P99)\",\n      \"type\": \"timeseries\"\n    },\n    {\n      \"datasource\": \"${DS_PROMETHEUS}\",\n      \"description\": \"Total number of retries handled by workqueue\",\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"continuous-GrYlRd\"\n          },\n          \"custom\": {\n            \"axisCenteredZero\": false,\n            \"axisColorMode\": \"text\",\n            \"axisLabel\": \"\",\n            \"axisPlacement\": \"auto\",\n            \"barAlignment\": 0,\n            \"drawStyle\": \"line\",\n            \"fillOpacity\": 20,\n            \"gradientMode\": \"scheme\",\n            \"hideFrom\": {\n              \"legend\": false,\n              \"tooltip\": false,\n              \"viz\": false\n            },\n            \"lineInterpolation\": \"smooth\",\n            \"lineWidth\": 3,\n            \"pointSize\": 5,\n            \"scaleDistribution\": {\n              \"type\": \"linear\"\n            },\n            \"showPoints\": \"auto\",\n            \"spanNulls\": false,\n            \"stacking\": {\n              \"group\": \"A\",\n              \"mode\": \"none\"\n            },\n            \"thresholdsStyle\": {\n              \"mode\": \"off\"\n            }\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          },\n          \"unit\": \"ops\"\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 10,\n        \"x\": 14,\n        \"y\": 18\n      },\n      \"id\": 17,\n      \"options\": {\n        \"legend\": {\n          \"calcs\": [],\n          \"displayMode\": \"table\",\n          \"placement\": \"bottom\",\n          \"showLegend\": true\n        },\n        \"tooltip\": {\n          \"mode\": \"single\",\n          \"sort\": \"none\"\n        }\n      },\n      \"targets\": [\n        {\n          \"datasource\": \"${DS_PROMETHEUS}\",\n          \"exemplar\": true,\n          \"expr\": \"sum(rate(workqueue_retries_total{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])) by (instance, name)\",\n          \"interval\": \"\",\n          \"legendFormat\": \"{{name}} {{instance}} \",\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Work Queue Retries Rate\",\n      \"type\": \"timeseries\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"job\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\"}, job)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"multi\": false,\n        \"name\": \"namespace\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total, namespace)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      },\n      {\n        \"current\": {\n          \"selected\": true,\n          \"text\": [\n            \"All\"\n          ],\n          \"value\": [\n            \"$__all\"\n          ]\n        },\n        \"datasource\": \"${DS_PROMETHEUS}\",\n        \"definition\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n        \"hide\": 2,\n        \"includeAll\": true,\n        \"label\": \"pod\",\n        \"multi\": true,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": {\n          \"query\": \"label_values(controller_runtime_reconcile_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}, pod)\",\n          \"refId\": \"StandardVariableQuery\"\n        },\n        \"refresh\": 2,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"type\": \"query\"\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-15m\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Controller-Runtime-Metrics\",\n  \"weekStart\": \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/grafana/custom-metrics/config.yaml",
    "content": "---\ncustomMetrics:\n#  - metric: # Raw custom metric (required)\n#    type:   # Metric type: counter/gauge/histogram (required)\n#    expr:   # Prom_ql for the metric (optional)\n#    unit:   # Unit of measurement, examples: s,none,bytes,percent,etc. (optional)\n#\n#\n# Example:\n# ---\n# customMetrics:\n#   - metric: foo_bar\n#     unit: none\n#     type: histogram\n#   \texpr: histogram_quantile(0.90, sum by(instance, le) (rate(foo_bar{job=\\\"$job\\\", namespace=\\\"$namespace\\\"}[5m])))\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/hack/boilerplate.go.txt",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/busybox_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/tools/events\"\n\t\"k8s.io/utils/ptr\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n)\n\nconst busyboxFinalizer = \"example.com.testproject.org/finalizer\"\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableBusybox represents the status of the Deployment reconciliation\n\ttypeAvailableBusybox = \"Available\"\n\t// typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are yet to occur.\n\ttypeDegradedBusybox = \"Degraded\"\n)\n\n// BusyboxReconciler reconciles a Busybox object\ntype BusyboxReconciler struct {\n\tclient.Client\n\tScheme   *runtime.Scheme\n\tRecorder events.EventRecorder\n}\n\n// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen\n// when the command <make manifests> is executed.\n// To know more about markers see: https://book.kubebuilder.io/reference/markers.html\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=busyboxes/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=busyboxes/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,namespace=project-v4-with-plugins-system,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,namespace=project-v4-with-plugins-system,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,namespace=project-v4-with-plugins-system,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the Busybox instance\n\t// The purpose is check if the Custom Resource for the Kind Busybox\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tbusybox := &examplecomv1alpha1.Busybox{}\n\terr := r.Get(ctx, req.NamespacedName, busybox)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Busybox resource not found, ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get busybox\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tif len(busybox.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the busybox Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Let's add a finalizer. Then, we can define some operations which should\n\t// occur before the custom resource is deleted.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers\n\tif !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {\n\t\tlog.Info(\"Adding finalizer for Busybox\")\n\t\tcontrollerutil.AddFinalizer(busybox, busyboxFinalizer)\n\t\tif err = r.Update(ctx, busybox); err != nil {\n\t\t\tlog.Error(err, \"Failed to update custom resource to add finalizer\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the Busybox instance is marked to be deleted, which is\n\t// indicated by the deletion timestamp being set.\n\tisBusyboxMarkedToBeDeleted := busybox.GetDeletionTimestamp() != nil\n\tif isBusyboxMarkedToBeDeleted {\n\t\tif controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) {\n\t\t\tlog.Info(\"Performing finalizer operations for Busybox before deleting CR\")\n\n\t\t\t// Let's add here a status \"Downgrade\" to reflect that this resource began its process to be terminated.\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox,\n\t\t\t\tStatus: metav1.ConditionUnknown, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Performing finalizer operations for the custom resource: %s \", busybox.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// Perform all operations required before removing the finalizer and allow\n\t\t\t// the Kubernetes API to remove the custom resource.\n\t\t\tr.doFinalizerOperationsForBusybox(busybox)\n\n\t\t\t// TODO(user): If you add operations to the doFinalizerOperationsForBusybox method\n\t\t\t// then you need to ensure that all worked fine before deleting and updating the Downgrade status\n\t\t\t// otherwise, you should requeue here.\n\n\t\t\t// Re-fetch the busybox Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox,\n\t\t\t\tStatus: metav1.ConditionTrue, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Finalizer operations for custom resource %s name were successfully accomplished\", busybox.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing finalizer for Busybox after successfully performing the operations\")\n\t\t\tif ok := controllerutil.RemoveFinalizer(busybox, busyboxFinalizer); !ok {\n\t\t\t\terr = fmt.Errorf(\"finalizer for Busybox was not removed\")\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tif err := r.Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForBusybox(busybox)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Busybox\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", busybox.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-triggered again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif busybox.Spec.Size != nil {\n\t\tdesiredReplicas = *busybox.Spec.Size\n\t}\n\n\t// The CRD API defines that the Busybox type have a BusyboxSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the busybox Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch busybox\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", busybox.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", busybox.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, busybox); err != nil {\n\t\tlog.Error(err, \"Failed to update Busybox status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// finalizeBusybox will perform the required operations before delete the CR.\nfunc (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alpha1.Busybox) {\n\t// TODO(user): Add the cleanup steps that the operator\n\t// needs to do before the CR can be deleted. Examples\n\t// of finalizers include performing backups and deleting\n\t// resources that are not owned by this CR, like a PVC.\n\n\t// Note: It is not recommended to use finalizers with the purpose of deleting resources which are\n\t// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,\n\t// are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference.\n\t// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.\n\t// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/\n\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name,\n\t\tcr.Namespace)\n}\n\n// deploymentForBusybox returns a Busybox Deployment object\nfunc (r *BusyboxReconciler) deploymentForBusybox(\n\tbusybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) {\n\tls := labelsForBusybox()\n\n\t// Get the Operand image\n\timage, err := imageForBusybox()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      busybox.Name,\n\t\t\tNamespace: busybox.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: busybox.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t// TODO(user): Uncomment the following code to configure the nodeAffinity expression\n\t\t\t\t\t// according to the platforms which are supported by your solution. It is considered\n\t\t\t\t\t// best practice to support multiple architectures. build your manager image using the\n\t\t\t\t\t// makefile target docker-buildx. Also, you can use docker manifest inspect <image>\n\t\t\t\t\t// to check what are the platforms supported.\n\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n\t\t\t\t\t// Affinity: &corev1.Affinity{\n\t\t\t\t\t//\t NodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t//\t\t RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t//\t\t\t NodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t//\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t MatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/arch\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/os\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"linux\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t },\n\t\t\t\t\t//\t\t \t },\n\t\t\t\t\t//\t\t },\n\t\t\t\t\t//\t },\n\t\t\t\t\t// },\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\t// IMPORTANT: seccomProfile was introduced with Kubernetes 1.19\n\t\t\t\t\t\t// If you are looking for to produce solutions to be supported\n\t\t\t\t\t\t// on lower versions you must remove this option.\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"busybox\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(busybox, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n\n// labelsForBusybox returns the labels for selecting the resources\n// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\nfunc labelsForBusybox() map[string]string {\n\tvar imageTag string\n\timage, err := imageForBusybox()\n\tif err == nil {\n\t\timageTag = strings.Split(image, \":\")[1]\n\t}\n\treturn map[string]string{\n\t\t\"app.kubernetes.io/name\":       \"project-v4-with-plugins\",\n\t\t\"app.kubernetes.io/version\":    imageTag,\n\t\t\"app.kubernetes.io/managed-by\": \"BusyboxController\",\n\t}\n}\n\n// imageForBusybox gets the Operand image which is managed by this controller\n// from the BUSYBOX_IMAGE environment variable defined in the config/manager/manager.yaml\nfunc imageForBusybox() (string, error) {\n\tvar imageEnvVar = \"BUSYBOX_IMAGE\"\n\timage, found := os.LookupEnv(imageEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"unable to find %s environment variable with the image\", imageEnvVar)\n\t}\n\treturn image, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\n// The whole idea is to be watching the resources that matter for the controller.\n// When a resource that the controller is interested in changes, the Watch triggers\n// the controller’s reconciliation loop, ensuring that the actual state of the resource\n// matches the desired state as defined in the controller’s logic.\n//\n// Notice how we configured the Manager to monitor events such as the creation, update,\n// or deletion of a Custom Resource (CR) of the Busybox kind, as well as any changes\n// to the Deployment that the controller manages and owns.\nfunc (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t// Watch the Busybox CR(s) and trigger reconciliation whenever it\n\t\t// is created, updated, or deleted\n\t\tFor(&examplecomv1alpha1.Busybox{}).\n\t\tNamed(\"busybox\").\n\t\t// Watch the Deployment managed by the BusyboxReconciler. If any changes occur to the Deployment\n\t\t// owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n\t\t// state aligns with the desired state. See that the ownerRef was set when the Deployment was created.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/busybox_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n)\n\nvar _ = Describe(\"Busybox controller\", func() {\n\tContext(\"Busybox controller test\", func() {\n\n\t\tconst BusyboxName = \"test-busybox\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      BusyboxName,\n\t\t\t\tNamespace: BusyboxName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      BusyboxName,\n\t\t\tNamespace: BusyboxName,\n\t\t}\n\t\tbusybox := &examplecomv1alpha1.Busybox{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Create(ctx, namespace)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Setting the Image ENV VAR which stores the Operand image\")\n\t\t\terr = os.Setenv(\"BUSYBOX_IMAGE\", \"example.com/image:test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating the custom resource for the Kind Busybox\")\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, busybox)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t// Let's mock our custom resource at the same way that we would\n\t\t\t\t// apply on the cluster the manifest under config/samples\n\t\t\t\tbusybox = &examplecomv1alpha1.Busybox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      BusyboxName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: examplecomv1alpha1.BusyboxSpec{\n\t\t\t\t\t\tSize: ptr.To(int32(1)),\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, busybox)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind Busybox\")\n\t\t\tfound := &examplecomv1alpha1.Busybox{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\n\t\t\tBy(\"Removing the Image ENV VAR which stores the Operand image\")\n\t\t\t_ = os.Unsetenv(\"BUSYBOX_IMAGE\")\n\t\t})\n\n\t\tIt(\"should successfully reconcile a custom resource for Busybox\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &examplecomv1alpha1.Busybox{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource created\")\n\t\t\tbusyboxReconciler := &BusyboxReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := busyboxReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = busyboxReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Busybox instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, busybox)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(busybox.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableBusybox)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableBusybox)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableBusybox)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableBusybox)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/memcached_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/tools/events\"\n\t\"k8s.io/utils/ptr\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n)\n\nconst memcachedFinalizer = \"example.com.testproject.org/finalizer\"\n\n// Definitions to manage status conditions\nconst (\n\t// typeAvailableMemcached represents the status of the Deployment reconciliation\n\ttypeAvailableMemcached = \"Available\"\n\t// typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are yet to occur.\n\ttypeDegradedMemcached = \"Degraded\"\n)\n\n// MemcachedReconciler reconciles a Memcached object\ntype MemcachedReconciler struct {\n\tclient.Client\n\tScheme   *runtime.Scheme\n\tRecorder events.EventRecorder\n}\n\n// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen\n// when the command <make manifests> is executed.\n// To know more about markers see: https://book.kubebuilder.io/reference/markers.html\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=memcacheds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=memcacheds/finalizers,verbs=update\n// +kubebuilder:rbac:groups=events.k8s.io,namespace=project-v4-with-plugins-system,resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=apps,namespace=project-v4-with-plugins-system,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=core,namespace=project-v4-with-plugins-system,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator\n// pattern you will create Controllers which provide a reconcile function\n// responsible for synchronizing resources until the desired state is reached on the cluster.\n// Breaking this recommendation goes against the design principles of controller-runtime.\n// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention.\n// For further info:\n// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/\n// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := logf.FromContext(ctx)\n\n\t// Fetch the Memcached instance\n\t// The purpose is check if the Custom Resource for the Kind Memcached\n\t// is applied on the cluster if not we return nil to stop the reconciliation\n\tmemcached := &examplecomv1alpha1.Memcached{}\n\terr := r.Get(ctx, req.NamespacedName, memcached)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// If the custom resource is not found then it usually means that it was deleted or not created\n\t\t\t// In this way, we will stop the reconciliation\n\t\t\tlog.Info(\"Memcached resource not found, ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get memcached\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\tif len(memcached.Status.Conditions) == 0 {\n\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: \"Reconciling\", Message: \"Starting reconciliation\"})\n\t\tif err = r.Status().Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Let's re-fetch the memcached Custom Resource after updating the status\n\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t// raising the error \"the object has been modified, please apply\n\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t// if we try to update it again in the following operations\n\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Let's add a finalizer. Then, we can define some operations which should\n\t// occur before the custom resource is deleted.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers\n\tif !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) {\n\t\tlog.Info(\"Adding finalizer for Memcached\")\n\t\tcontrollerutil.AddFinalizer(memcached, memcachedFinalizer)\n\t\tif err = r.Update(ctx, memcached); err != nil {\n\t\t\tlog.Error(err, \"Failed to update custom resource to add finalizer\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\t// Check if the Memcached instance is marked to be deleted, which is\n\t// indicated by the deletion timestamp being set.\n\tisMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil\n\tif isMemcachedMarkedToBeDeleted {\n\t\tif controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) {\n\t\t\tlog.Info(\"Performing finalizer operations for Memcached before deleting CR\")\n\n\t\t\t// Let's add here a status \"Downgrade\" to reflect that this resource began its process to be terminated.\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached,\n\t\t\t\tStatus: metav1.ConditionUnknown, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Performing finalizer operations for the custom resource: %s \", memcached.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// Perform all operations required before removing the finalizer and allow\n\t\t\t// the Kubernetes API to remove the custom resource.\n\t\t\tr.doFinalizerOperationsForMemcached(memcached)\n\n\t\t\t// TODO(user): If you add operations to the doFinalizerOperationsForMemcached method\n\t\t\t// then you need to ensure that all worked fine before deleting and updating the Downgrade status\n\t\t\t// otherwise, you should requeue here.\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached,\n\t\t\t\tStatus: metav1.ConditionTrue, Reason: \"Finalizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Finalizer operations for custom resource %s name were successfully accomplished\", memcached.Name)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing finalizer for Memcached after successfully performing the operations\")\n\t\t\tif ok := controllerutil.RemoveFinalizer(memcached, memcachedFinalizer); !ok {\n\t\t\t\terr = fmt.Errorf(\"finalizer for Memcached was not removed\")\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\tif err := r.Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to remove finalizer for Memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := &appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil && apierrors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep, err := r.deploymentForMemcached(memcached)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to define new Deployment resource for Memcached\")\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Reconciling\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to create Deployment for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\tlog.Info(\"Creating a new Deployment\",\n\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\tif err = r.Create(ctx, dep); err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\",\n\t\t\t\t\"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Deployment created successfully\n\t\t// We will requeue the reconciliation so that we can ensure the state\n\t\t// and move forward for the next operations\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\t// Let's return the error for the reconciliation be re-triggered again\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// If the size is not defined in the Custom Resource then we will set the desired replicas to 0\n\tvar desiredReplicas int32 = 0\n\tif memcached.Spec.Size != nil {\n\t\tdesiredReplicas = *memcached.Spec.Size\n\t}\n\n\t// The CRD API defines that the Memcached type have a MemcachedSpec.Size field\n\t// to set the quantity of Deployment instances to the desired state on the cluster.\n\t// Therefore, the following code will ensure the Deployment size is the same as defined\n\t// via the Size spec of the Custom Resource which we are reconciling.\n\tif found.Spec.Replicas == nil || *found.Spec.Replicas != desiredReplicas {\n\t\tfound.Spec.Replicas = ptr.To(desiredReplicas)\n\t\tif err = r.Update(ctx, found); err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\",\n\t\t\t\t\"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\n\t\t\t// Re-fetch the memcached Custom Resource before updating the status\n\t\t\t// so that we have the latest state of the resource on the cluster and we will avoid\n\t\t\t// raising the error \"the object has been modified, please apply\n\t\t\t// your changes to the latest version and try again\" which would re-trigger the reconciliation\n\t\t\tif err := r.Get(ctx, req.NamespacedName, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to re-fetch memcached\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\t// The following implementation will update the status\n\t\t\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\t\t\tStatus: metav1.ConditionFalse, Reason: \"Resizing\",\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to update the size for the custom resource (%s): (%s)\", memcached.Name, err)})\n\n\t\t\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\t\treturn ctrl.Result{}, err\n\t\t\t}\n\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\n\t\t// Now, that we update the size we want to requeue the reconciliation\n\t\t// so that we can ensure that we have the latest state of the resource before\n\t\t// update. Also, it will help ensure the desired state on the cluster\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// The following implementation will update the status\n\tmeta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached,\n\t\tStatus: metav1.ConditionTrue, Reason: \"Reconciling\",\n\t\tMessage: fmt.Sprintf(\"Deployment for custom resource (%s) with %d replicas created successfully\", memcached.Name, desiredReplicas)})\n\n\tif err := r.Status().Update(ctx, memcached); err != nil {\n\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// finalizeMemcached will perform the required operations before delete the CR.\nfunc (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1alpha1.Memcached) {\n\t// TODO(user): Add the cleanup steps that the operator\n\t// needs to do before the CR can be deleted. Examples\n\t// of finalizers include performing backups and deleting\n\t// resources that are not owned by this CR, like a PVC.\n\n\t// Note: It is not recommended to use finalizers with the purpose of deleting resources which are\n\t// created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile,\n\t// are defined as dependent of the custom resource. See that we use the method ctrl.SetControllerReference.\n\t// to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API.\n\t// More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/\n\n\t// The following implementation will raise an event\n\tr.Recorder.Eventf(cr, nil, corev1.EventTypeWarning, \"Deleting\", \"DeleteCR\",\n\t\t\"Custom Resource %s is being deleted from the namespace %s\",\n\t\tcr.Name,\n\t\tcr.Namespace)\n}\n\n// deploymentForMemcached returns a Memcached Deployment object\nfunc (r *MemcachedReconciler) deploymentForMemcached(\n\tmemcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) {\n\tls := labelsForMemcached()\n\n\t// Get the Operand image\n\timage, err := imageForMemcached()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdep := &appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      memcached.Name,\n\t\t\tNamespace: memcached.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: memcached.Spec.Size,\n\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t// TODO(user): Uncomment the following code to configure the nodeAffinity expression\n\t\t\t\t\t// according to the platforms which are supported by your solution. It is considered\n\t\t\t\t\t// best practice to support multiple architectures. build your manager image using the\n\t\t\t\t\t// makefile target docker-buildx. Also, you can use docker manifest inspect <image>\n\t\t\t\t\t// to check what are the platforms supported.\n\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity\n\t\t\t\t\t// Affinity: &corev1.Affinity{\n\t\t\t\t\t//\t NodeAffinity: &corev1.NodeAffinity{\n\t\t\t\t\t//\t\t RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\t\t//\t\t\t NodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t//\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t MatchExpressions: []corev1.NodeSelectorRequirement{\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/arch\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"amd64\", \"arm64\", \"ppc64le\", \"s390x\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t\t {\n\t\t\t\t\t//\t\t\t\t\t\t\t Key:      \"kubernetes.io/os\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Operator: \"In\",\n\t\t\t\t\t//\t\t\t\t\t\t\t Values:   []string{\"linux\"},\n\t\t\t\t\t//\t\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t\t },\n\t\t\t\t\t//\t\t\t\t },\n\t\t\t\t\t//\t\t \t },\n\t\t\t\t\t//\t\t },\n\t\t\t\t\t//\t },\n\t\t\t\t\t// },\n\t\t\t\t\tSecurityContext: &corev1.PodSecurityContext{\n\t\t\t\t\t\tRunAsNonRoot: ptr.To(true),\n\t\t\t\t\t\t// IMPORTANT: seccomProfile was introduced with Kubernetes 1.19\n\t\t\t\t\t\t// If you are looking for to produce solutions to be supported\n\t\t\t\t\t\t// on lower versions you must remove this option.\n\t\t\t\t\t\tSeccompProfile: &corev1.SeccompProfile{\n\t\t\t\t\t\t\tType: corev1.SeccompProfileTypeRuntimeDefault,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:           image,\n\t\t\t\t\t\tName:            \"memcached\",\n\t\t\t\t\t\tImagePullPolicy: corev1.PullIfNotPresent,\n\t\t\t\t\t\t// Ensure restrictive context for the container\n\t\t\t\t\t\t// More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\t\t\t\t\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\t\t\t\t\tRunAsNonRoot:             ptr.To(true),\n\t\t\t\t\t\t\tRunAsUser:                ptr.To(int64(1001)),\n\t\t\t\t\t\t\tAllowPrivilegeEscalation: ptr.To(false),\n\t\t\t\t\t\t\tCapabilities: &corev1.Capabilities{\n\t\t\t\t\t\t\t\tDrop: []corev1.Capability{\n\t\t\t\t\t\t\t\t\t\"ALL\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: memcached.Spec.ContainerPort,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"--memory-limit=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Set the ownerRef for the Deployment\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/\n\tif err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil {\n\t\treturn nil, err\n\t}\n\treturn dep, nil\n}\n\n// labelsForMemcached returns the labels for selecting the resources\n// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\nfunc labelsForMemcached() map[string]string {\n\tvar imageTag string\n\timage, err := imageForMemcached()\n\tif err == nil {\n\t\timageTag = strings.Split(image, \":\")[1]\n\t}\n\treturn map[string]string{\n\t\t\"app.kubernetes.io/name\":       \"project-v4-with-plugins\",\n\t\t\"app.kubernetes.io/version\":    imageTag,\n\t\t\"app.kubernetes.io/managed-by\": \"MemcachedController\",\n\t}\n}\n\n// imageForMemcached gets the Operand image which is managed by this controller\n// from the MEMCACHED_IMAGE environment variable defined in the config/manager/manager.yaml\nfunc imageForMemcached() (string, error) {\n\tvar imageEnvVar = \"MEMCACHED_IMAGE\"\n\timage, found := os.LookupEnv(imageEnvVar)\n\tif !found {\n\t\treturn \"\", fmt.Errorf(\"unable to find %s environment variable with the image\", imageEnvVar)\n\t}\n\treturn image, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\n// The whole idea is to be watching the resources that matter for the controller.\n// When a resource that the controller is interested in changes, the Watch triggers\n// the controller’s reconciliation loop, ensuring that the actual state of the resource\n// matches the desired state as defined in the controller’s logic.\n//\n// Notice how we configured the Manager to monitor events such as the creation, update,\n// or deletion of a Custom Resource (CR) of the Memcached kind, as well as any changes\n// to the Deployment that the controller manages and owns.\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\t// Watch the Memcached CR(s) and trigger reconciliation whenever it\n\t\t// is created, updated, or deleted\n\t\tFor(&examplecomv1alpha1.Memcached{}).\n\t\tNamed(\"memcached\").\n\t\t// Watch the Deployment managed by the MemcachedReconciler. If any changes occur to the Deployment\n\t\t// owned and managed by this controller, it will trigger reconciliation, ensuring that the cluster\n\t\t// state aligns with the desired state. See that the ownerRef was set when the Deployment was created.\n\t\tOwns(&appsv1.Deployment{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/memcached_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n)\n\nvar _ = Describe(\"Memcached controller\", func() {\n\tContext(\"Memcached controller test\", func() {\n\n\t\tconst MemcachedName = \"test-memcached\"\n\n\t\tctx := context.Background()\n\n\t\tnamespace := &corev1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      MemcachedName,\n\t\t\t\tNamespace: MemcachedName,\n\t\t\t},\n\t\t}\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      MemcachedName,\n\t\t\tNamespace: MemcachedName,\n\t\t}\n\t\tmemcached := &examplecomv1alpha1.Memcached{}\n\n\t\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\t\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"Creating the Namespace to perform the tests\")\n\t\t\terr := k8sClient.Create(ctx, namespace)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Setting the Image ENV VAR which stores the Operand image\")\n\t\t\terr = os.Setenv(\"MEMCACHED_IMAGE\", \"example.com/image:test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"creating the custom resource for the Kind Memcached\")\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, memcached)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\t// Let's mock our custom resource at the same way that we would\n\t\t\t\t// apply on the cluster the manifest under config/samples\n\t\t\t\tmemcached = &examplecomv1alpha1.Memcached{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      MemcachedName,\n\t\t\t\t\t\tNamespace: namespace.Name,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: examplecomv1alpha1.MemcachedSpec{\n\t\t\t\t\t\tSize:          ptr.To(int32(1)),\n\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\terr = k8sClient.Create(ctx, memcached)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\tBy(\"removing the custom resource for the Kind Memcached\")\n\t\t\tfound := &examplecomv1alpha1.Memcached{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, found)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tg.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\t// TODO(user): Attention if you improve this code by adding other context test you MUST\n\t\t\t// be aware of the current delete namespace limitations.\n\t\t\t// More info: https://book.kubebuilder.io/reference/envtest.html#testing-considerations\n\t\t\tBy(\"Deleting the Namespace to perform the tests\")\n\t\t\t_ = k8sClient.Delete(ctx, namespace)\n\n\t\t\tBy(\"Removing the Image ENV VAR which stores the Operand image\")\n\t\t\t_ = os.Unsetenv(\"MEMCACHED_IMAGE\")\n\t\t})\n\n\t\tIt(\"should successfully reconcile a custom resource for Memcached\", func() {\n\t\t\tBy(\"Checking if the custom resource was successfully created\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &examplecomv1alpha1.Memcached{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource created\")\n\t\t\tmemcachedReconciler := &MemcachedReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := memcachedReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking if Deployment was successfully created in the reconciliation\")\n\t\t\tEventually(func(g Gomega) {\n\t\t\t\tfound := &appsv1.Deployment{}\n\t\t\t\tg.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())\n\t\t\t}).Should(Succeed())\n\n\t\t\tBy(\"Reconciling the custom resource again\")\n\t\t\t_, err = memcachedReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Checking the latest Status Condition added to the Memcached instance\")\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())\n\t\t\tvar conditions []metav1.Condition\n\t\t\tExpect(memcached.Status.Conditions).To(ContainElement(\n\t\t\t\tHaveField(\"Type\", Equal(typeAvailableMemcached)), &conditions))\n\t\t\tExpect(conditions).To(HaveLen(1), \"Multiple conditions of type %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Status).To(Equal(metav1.ConditionTrue), \"condition %s\", typeAvailableMemcached)\n\t\t\tExpect(conditions[0].Reason).To(Equal(\"Reconciling\"), \"condition %s\", typeAvailableMemcached)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttestEnv   *envtest.Environment\n\tcfg       *rest.Config\n\tk8sClient client.Client\n)\n\nfunc TestControllers(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Controller Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = examplecomv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: true,\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/wordpress_controller.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n)\n\n// WordpressReconciler reconciles a Wordpress object\ntype WordpressReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=wordpresses,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=wordpresses/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=example.com.testproject.org,namespace=project-v4-with-plugins-system,resources=wordpresses/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Wordpress object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.23.3/pkg/reconcile\nfunc (r *WordpressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = logf.FromContext(ctx)\n\n\t// TODO(user): your logic here\n\n\treturn ctrl.Result{}, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *WordpressReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&examplecomv1.Wordpress{}).\n\t\tNamed(\"wordpress\").\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/controller/wordpress_controller_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controller\n\nimport (\n\t\"context\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n)\n\nvar _ = Describe(\"Wordpress Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-resource\"\n\n\t\tctx := context.Background()\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\", // TODO(user):Modify as needed\n\t\t}\n\t\twordpress := &examplecomv1.Wordpress{}\n\n\t\tBeforeEach(func() {\n\t\t\tBy(\"creating the custom resource for the Kind Wordpress\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, wordpress)\n\t\t\tif err != nil && errors.IsNotFound(err) {\n\t\t\t\tresource := &examplecomv1.Wordpress{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// TODO(user): Specify other spec details if needed.\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func() {\n\t\t\t// TODO(user): Cleanup logic after each test, like removing the resource instance.\n\t\t\tresource := &examplecomv1.Wordpress{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"Cleanup the specific resource instance Wordpress\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).To(Succeed())\n\t\t})\n\t\tIt(\"should successfully reconcile the resource\", func() {\n\t\t\tBy(\"Reconciling the created resource\")\n\t\t\tcontrollerReconciler := &WordpressReconciler{\n\t\t\t\tClient: k8sClient,\n\t\t\t\tScheme: k8sClient.Scheme(),\n\t\t\t}\n\n\t\t\t_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.\n\t\t\t// Example: If you expect a certain status condition after reconciliation, verify it here.\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupWordpressWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1/wordpress_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar wordpresslog = logf.Log.WithName(\"wordpress-resource\")\n\n// SetupWordpressWebhookWithManager registers the webhook for Wordpress in the manager.\nfunc SetupWordpressWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &examplecomv1.Wordpress{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1/wordpress_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\texamplecomv1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Wordpress Webhook\", func() {\n\tvar (\n\t\tobj    *examplecomv1.Wordpress\n\t\toldObj *examplecomv1.Wordpress\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &examplecomv1.Wordpress{}\n\t\toldObj = &examplecomv1.Wordpress{}\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating Wordpress under Conversion Webhook\", func() {\n\t\t// TODO (user): Add logic to convert the object to the desired version and verify the conversion\n\t\t// Example:\n\t\t// It(\"Should convert the object correctly\", func() {\n\t\t//     convertedObj := &examplecomv1.Wordpress{}\n\t\t//     Expect(obj.ConvertTo(convertedObj)).To(Succeed())\n\t\t//     Expect(convertedObj).ToNot(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n)\n\n// nolint:unused\n// log is for logging in this package.\nvar memcachedlog = logf.Log.WithName(\"memcached-resource\")\n\n// SetupMemcachedWebhookWithManager registers the webhook for Memcached in the manager.\nfunc SetupMemcachedWebhookWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewWebhookManagedBy(mgr, &examplecomv1alpha1.Memcached{}).\n\t\tWithValidator(&MemcachedCustomValidator{}).\n\t\tComplete()\n}\n\n// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n\n// TODO(user): change verbs to \"verbs=create;update;delete\" if you want to enable deletion validation.\n// NOTE: If you want to customise the 'path', use the flags '--defaulting-path' or '--validation-path'.\n// +kubebuilder:webhook:path=/validate-example-com-testproject-org-v1alpha1-memcached,mutating=false,failurePolicy=fail,sideEffects=None,groups=example.com.testproject.org,resources=memcacheds,verbs=create;update,versions=v1alpha1,name=vmemcached-v1alpha1.kb.io,admissionReviewVersions=v1\n\n// MemcachedCustomValidator struct is responsible for validating the Memcached resource\n// when it is created, updated, or deleted.\n//\n// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,\n// as this struct is used only for temporary operations and does not need to be deeply copied.\ntype MemcachedCustomValidator struct {\n\t// TODO(user): Add more fields as needed for validation\n}\n\n// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateCreate(_ context.Context, obj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon creation\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object creation.\n\n\treturn nil, nil\n}\n\n// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon update\", \"name\", newObj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object update.\n\n\treturn nil, nil\n}\n\n// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Memcached.\nfunc (v *MemcachedCustomValidator) ValidateDelete(_ context.Context, obj *examplecomv1alpha1.Memcached) (admission.Warnings, error) {\n\tmemcachedlog.Info(\"Validation for Memcached upon deletion\", \"name\", obj.GetName())\n\n\t// TODO(user): fill in your validation logic upon object deletion.\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1alpha1/memcached_webhook_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n\t// TODO (user): Add any additional imports if needed\n)\n\nvar _ = Describe(\"Memcached Webhook\", func() {\n\tvar (\n\t\tobj       *examplecomv1alpha1.Memcached\n\t\toldObj    *examplecomv1alpha1.Memcached\n\t\tvalidator MemcachedCustomValidator\n\t)\n\n\tBeforeEach(func() {\n\t\tobj = &examplecomv1alpha1.Memcached{}\n\t\toldObj = &examplecomv1alpha1.Memcached{}\n\t\tvalidator = MemcachedCustomValidator{}\n\t\tExpect(validator).NotTo(BeNil(), \"Expected validator to be initialized\")\n\t\tExpect(oldObj).NotTo(BeNil(), \"Expected oldObj to be initialized\")\n\t\tExpect(obj).NotTo(BeNil(), \"Expected obj to be initialized\")\n\t})\n\n\tAfterEach(func() {\n\t\t// TODO (user): Add any teardown logic common to all tests\n\t})\n\n\tContext(\"When creating or updating Memcached under Validating Webhook\", func() {\n\t\t// TODO (user): Add logic for validating webhooks\n\t\t// Example:\n\t\t// It(\"Should deny creation if a required field is missing\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())\n\t\t// })\n\t\t//\n\t\t// It(\"Should admit creation if all required fields are present\", func() {\n\t\t//     By(\"simulating an invalid creation scenario\")\n\t\t//     obj.SomeRequiredField = \"valid_value\"\n\t\t//     Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())\n\t\t// })\n\t\t//\n\t\t// It(\"Should validate updates correctly\", func() {\n\t\t//     By(\"simulating a valid update scenario\")\n\t\t//     oldObj.SomeRequiredField = \"updated_value\"\n\t\t//     obj.SomeRequiredField = \"updated_value\"\n\t\t//     Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())\n\t\t// })\n\t})\n\n})\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/internal/webhook/v1alpha1/webhook_suite_test.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\texamplecomv1alpha1 \"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/api/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar (\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\tk8sClient client.Client\n\tcfg       *rest.Config\n\ttestEnv   *envtest.Environment\n)\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecs(t, \"Webhook Suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tlogf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))\n\n\tctx, cancel = context.WithCancel(context.TODO())\n\n\tvar err error\n\terr = examplecomv1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"crd\", \"bases\")},\n\t\tErrorIfCRDPathMissing: false,\n\n\t\tWebhookInstallOptions: envtest.WebhookInstallOptions{\n\t\t\tPaths: []string{filepath.Join(\"..\", \"..\", \"..\", \"config\", \"webhook\")},\n\t\t},\n\t}\n\n\t// Retrieve the first found binary directory to allow running tests from IDEs\n\tif getFirstFoundEnvTestBinaryDir() != \"\" {\n\t\ttestEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()\n\t}\n\n\t// cfg is defined in this file globally.\n\tcfg, err = testEnv.Start()\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(cfg).NotTo(BeNil())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\t// start webhook server using Manager.\n\twebhookInstallOptions := &testEnv.WebhookInstallOptions\n\tmgr, err := ctrl.NewManager(cfg, ctrl.Options{\n\t\tScheme: scheme.Scheme,\n\t\tWebhookServer: webhook.NewServer(webhook.Options{\n\t\t\tHost:    webhookInstallOptions.LocalServingHost,\n\t\t\tPort:    webhookInstallOptions.LocalServingPort,\n\t\t\tCertDir: webhookInstallOptions.LocalServingCertDir,\n\t\t}),\n\t\tLeaderElection: false,\n\t\tMetrics:        metricsserver.Options{BindAddress: \"0\"},\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\terr = SetupMemcachedWebhookWithManager(mgr)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:webhook\n\n\tgo func() {\n\t\tdefer GinkgoRecover()\n\t\terr = mgr.Start(ctx)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t}()\n\n\t// wait for the webhook server to get ready.\n\tdialer := &net.Dialer{Timeout: time.Second}\n\taddrPort := fmt.Sprintf(\"%s:%d\", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)\n\tEventually(func() error {\n\t\tconn, err := tls.DialWithDialer(dialer, \"tcp\", addrPort, &tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn conn.Close()\n\t}).Should(Succeed())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\tcancel()\n\tEventually(func() error {\n\t\treturn testEnv.Stop()\n\t}, time.Minute, time.Second).Should(Succeed())\n})\n\n// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.\n// ENVTEST-based tests depend on specific binaries, usually located in paths set by\n// controller-runtime. When running tests directly (e.g., via an IDE) without using\n// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.\n//\n// This function streamlines the process by finding the required binaries, similar to\n// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are\n// properly set up, run 'make setup-envtest' beforehand.\nfunc getFirstFoundEnvTestBinaryDir() string {\n\tbasePath := filepath.Join(\"..\", \"..\", \"..\", \"bin\", \"k8s\")\n\tentries, err := os.ReadDir(basePath)\n\tif err != nil {\n\t\tlogf.Log.Error(err, \"Failed to read directory\", \"path\", basePath)\n\t\treturn \"\"\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\treturn filepath.Join(basePath, entry.Name())\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/test/e2e/e2e_suite_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/test/utils\"\n)\n\nvar (\n\t// managerImage is the manager image to be built and loaded for testing.\n\tmanagerImage = \"example.com/project-v4-with-plugins:v0.0.1\"\n\t// shouldCleanupCertManager tracks whether CertManager was installed by this suite.\n\tshouldCleanupCertManager = false\n)\n\n// TestE2E runs the e2e test suite to validate the solution in an isolated environment.\n// The default setup requires Kind and CertManager.\n//\n// To skip CertManager installation, set: CERT_MANAGER_INSTALL_SKIP=true\nfunc TestE2E(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"Starting project-v4-with-plugins e2e test suite\\n\")\n\tRunSpecs(t, \"e2e suite\")\n}\n\nvar _ = BeforeSuite(func() {\n\tBy(\"building the manager image\")\n\tcmd := exec.Command(\"make\", \"docker-build\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t_, err := utils.Run(cmd)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to build the manager image\")\n\n\t// TODO(user): If you want to change the e2e test vendor from Kind,\n\t// ensure the image is built and available, then remove the following block.\n\tBy(\"loading the manager image on Kind\")\n\terr = utils.LoadImageToKindClusterWithName(managerImage)\n\tExpectWithOffset(1, err).NotTo(HaveOccurred(), \"Failed to load the manager image into Kind\")\n\n\tsetupCertManager()\n})\n\nvar _ = AfterSuite(func() {\n\tteardownCertManager()\n})\n\n// setupCertManager installs CertManager if needed for webhook tests.\n// Skips installation if CERT_MANAGER_INSTALL_SKIP=true or if already present.\nfunc setupCertManager() {\n\tif os.Getenv(\"CERT_MANAGER_INSTALL_SKIP\") == \"true\" {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager installation (CERT_MANAGER_INSTALL_SKIP=true)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"checking if CertManager is already installed\")\n\tif utils.IsCertManagerCRDsInstalled() {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"CertManager is already installed. Skipping installation.\\n\")\n\t\treturn\n\t}\n\n\t// Mark for cleanup before installation to handle interruptions and partial installs.\n\tshouldCleanupCertManager = true\n\n\tBy(\"installing CertManager\")\n\tExpect(utils.InstallCertManager()).To(Succeed(), \"Failed to install CertManager\")\n}\n\n// teardownCertManager uninstalls CertManager if it was installed by setupCertManager.\n// This ensures we only remove what we installed.\nfunc teardownCertManager() {\n\tif !shouldCleanupCertManager {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Skipping CertManager cleanup (not installed by this suite)\\n\")\n\t\treturn\n\t}\n\n\tBy(\"uninstalling CertManager\")\n\tutils.UninstallCertManager()\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/test/e2e/e2e_test.go",
    "content": "//go:build e2e\n// +build e2e\n\n/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage e2e\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t\"sigs.k8s.io/kubebuilder/testdata/project-v4-with-plugins/test/utils\"\n)\n\n// namespace where the project is deployed in\nconst namespace = \"project-v4-with-plugins-system\"\n\n// serviceAccountName created for the project\nconst serviceAccountName = \"project-v4-with-plugins-controller-manager\"\n\n// metricsServiceName is the name of the metrics service of the project\nconst metricsServiceName = \"project-v4-with-plugins-controller-manager-metrics-service\"\n\n// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data\nconst metricsRoleBindingName = \"project-v4-with-plugins-metrics-binding\"\n\nvar _ = Describe(\"Manager\", Ordered, func() {\n\tvar controllerPodName string\n\n\t// Before running the tests, set up the environment by creating the namespace,\n\t// enforce the restricted security policy to the namespace, installing CRDs,\n\t// and deploying the controller.\n\tBeforeAll(func() {\n\t\tBy(\"creating manager namespace\")\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"ns\", namespace)\n\t\t_, err := utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create namespace\")\n\n\t\tBy(\"labeling the namespace to enforce the restricted security policy\")\n\t\tcmd = exec.Command(\"kubectl\", \"label\", \"--overwrite\", \"ns\", namespace,\n\t\t\t\"pod-security.kubernetes.io/enforce=restricted\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to label namespace with restricted policy\")\n\n\t\tBy(\"installing CRDs\")\n\t\tcmd = exec.Command(\"make\", \"install\")\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to install CRDs\")\n\n\t\tBy(\"deploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"deploy\", fmt.Sprintf(\"IMG=%s\", managerImage))\n\t\t_, err = utils.Run(cmd)\n\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to deploy the controller-manager\")\n\t})\n\n\t// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,\n\t// and deleting the namespace.\n\tAfterAll(func() {\n\t\tBy(\"cleaning up the curl pod for metrics\")\n\t\tcmd := exec.Command(\"kubectl\", \"delete\", \"pod\", \"curl-metrics\", \"-n\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"undeploying the controller-manager\")\n\t\tcmd = exec.Command(\"make\", \"undeploy\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"uninstalling CRDs\")\n\t\tcmd = exec.Command(\"make\", \"uninstall\")\n\t\t_, _ = utils.Run(cmd)\n\n\t\tBy(\"removing manager namespace\")\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"ns\", namespace)\n\t\t_, _ = utils.Run(cmd)\n\t})\n\n\t// After each test, check for failures and collect logs, events,\n\t// and pod descriptions for debugging.\n\tAfterEach(func() {\n\t\tspecReport := CurrentSpecReport()\n\t\tif specReport.Failed() {\n\t\t\tBy(\"Fetching controller manager pod logs\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\tcontrollerLogs, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Controller logs:\\n %s\", controllerLogs)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Controller logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching Kubernetes events\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"events\", \"-n\", namespace, \"--sort-by=.lastTimestamp\")\n\t\t\teventsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Kubernetes events:\\n%s\", eventsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get Kubernetes events: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching curl-metrics logs\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\t\t\tmetricsOutput, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Metrics logs:\\n %s\", metricsOutput)\n\t\t\t} else {\n\t\t\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"Failed to get curl-metrics logs: %s\", err)\n\t\t\t}\n\n\t\t\tBy(\"Fetching controller manager pod description\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"describe\", \"pod\", controllerPodName, \"-n\", namespace)\n\t\t\tpodDescription, err := utils.Run(cmd)\n\t\t\tif err == nil {\n\t\t\t\tfmt.Println(\"Pod description:\\n\", podDescription)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Failed to describe controller pod\")\n\t\t\t}\n\t\t}\n\t})\n\n\tSetDefaultEventuallyTimeout(2 * time.Minute)\n\tSetDefaultEventuallyPollingInterval(time.Second)\n\n\tContext(\"Manager\", func() {\n\t\tIt(\"should run successfully\", func() {\n\t\t\tBy(\"validating that the controller-manager pod is running as expected\")\n\t\t\tverifyControllerUp := func(g Gomega) {\n\t\t\t\t// Get the name of the controller-manager pod\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", \"-l\", \"control-plane=controller-manager\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .items }}\"+\n\t\t\t\t\t\t\"{{ if not .metadata.deletionTimestamp }}\"+\n\t\t\t\t\t\t\"{{ .metadata.name }}\"+\n\t\t\t\t\t\t\"{{ \\\"\\\\n\\\" }}{{ end }}{{ end }}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\n\t\t\t\tpodOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve controller-manager pod information\")\n\t\t\t\tpodNames := utils.GetNonEmptyLines(podOutput)\n\t\t\t\tg.Expect(podNames).To(HaveLen(1), \"expected 1 controller pod running\")\n\t\t\t\tcontrollerPodName = podNames[0]\n\t\t\t\tg.Expect(controllerPodName).To(ContainSubstring(\"controller-manager\"))\n\n\t\t\t\t// Validate the pod's status\n\t\t\t\tcmd = exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"pods\", controllerPodName, \"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace,\n\t\t\t\t)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Running\"), \"Incorrect controller-manager pod status\")\n\t\t\t}\n\t\t\tEventually(verifyControllerUp).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should ensure the metrics endpoint is serving metrics\", func() {\n\t\t\tBy(\"creating a ClusterRoleBinding for the service account to allow access to metrics\")\n\t\t\tcmd := exec.Command(\"kubectl\", \"create\", \"clusterrolebinding\", metricsRoleBindingName,\n\t\t\t\t\"--clusterrole=project-v4-with-plugins-metrics-reader\",\n\t\t\t\tfmt.Sprintf(\"--serviceaccount=%s:%s\", namespace, serviceAccountName),\n\t\t\t)\n\t\t\t_, err := utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create ClusterRoleBinding\")\n\n\t\t\tBy(\"validating that the metrics service is available\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"get\", \"service\", metricsServiceName, \"-n\", namespace)\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Metrics service should exist\")\n\n\t\t\tBy(\"getting the service account token\")\n\t\t\ttoken, err := serviceAccountToken()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(token).NotTo(BeEmpty())\n\n\t\t\tBy(\"ensuring the controller pod is ready\")\n\t\t\tverifyControllerPodReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pod\", controllerPodName, \"-n\", namespace,\n\t\t\t\t\t\"-o\", \"jsonpath={.status.conditions[?(@.type=='Ready')].status}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"True\"), \"Controller pod not ready\")\n\t\t\t}\n\t\t\tEventually(verifyControllerPodReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying that the controller manager is serving the metrics server\")\n\t\t\tverifyMetricsServerStarted := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"logs\", controllerPodName, \"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(ContainSubstring(\"Serving metrics server\"),\n\t\t\t\t\t\"Metrics server not yet started\")\n\t\t\t}\n\t\t\tEventually(verifyMetricsServerStarted, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting for the webhook service endpoints to be ready\")\n\t\t\tverifyWebhookEndpointsReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"endpointslices.discovery.k8s.io\", \"-n\", namespace,\n\t\t\t\t\t\"-l\", \"kubernetes.io/service-name=project-v4-with-plugins-webhook-service\",\n\t\t\t\t\t\"-o\", \"jsonpath={range .items[*]}{range .endpoints[*]}{.addresses[*]}{end}{end}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Webhook endpoints should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Webhook endpoints not yet ready\")\n\t\t\t}\n\t\t\tEventually(verifyWebhookEndpointsReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"verifying the validating webhook server is ready\")\n\t\t\tverifyValidatingWebhookReady := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-with-plugins-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"jsonpath={.webhooks[0].clientConfig.caBundle}\")\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"ValidatingWebhookConfiguration should exist\")\n\t\t\t\tg.Expect(output).ShouldNot(BeEmpty(), \"Validating webhook CA bundle not yet injected\")\n\t\t\t}\n\t\t\tEventually(verifyValidatingWebhookReady, 3*time.Minute, time.Second).Should(Succeed())\n\n\t\t\tBy(\"waiting additional time for webhook server to stabilize\")\n\t\t\ttime.Sleep(5 * time.Second)\n\n\t\t\t// +kubebuilder:scaffold:e2e-metrics-webhooks-readiness\n\n\t\t\tBy(\"creating the curl-metrics pod to access the metrics endpoint\")\n\t\t\tcmd = exec.Command(\"kubectl\", \"run\", \"curl-metrics\", \"--restart=Never\",\n\t\t\t\t\"--namespace\", namespace,\n\t\t\t\t\"--image=curlimages/curl:latest\",\n\t\t\t\t\"--overrides\",\n\t\t\t\tfmt.Sprintf(`{\n\t\t\t\t\t\"spec\": {\n\t\t\t\t\t\t\"containers\": [{\n\t\t\t\t\t\t\t\"name\": \"curl\",\n\t\t\t\t\t\t\t\"image\": \"curlimages/curl:latest\",\n\t\t\t\t\t\t\t\"command\": [\"/bin/sh\", \"-c\"],\n\t\t\t\t\t\t\t\"args\": [\n\t\t\t\t\t\t\t\t\"for i in $(seq 1 30); do curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics && exit 0 || sleep 2; done; exit 1\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"securityContext\": {\n\t\t\t\t\t\t\t\t\"readOnlyRootFilesystem\": true,\n\t\t\t\t\t\t\t\t\"allowPrivilegeEscalation\": false,\n\t\t\t\t\t\t\t\t\"capabilities\": {\n\t\t\t\t\t\t\t\t\t\"drop\": [\"ALL\"]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"runAsNonRoot\": true,\n\t\t\t\t\t\t\t\t\"runAsUser\": 1000,\n\t\t\t\t\t\t\t\t\"seccompProfile\": {\n\t\t\t\t\t\t\t\t\t\"type\": \"RuntimeDefault\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}],\n\t\t\t\t\t\t\"serviceAccountName\": \"%s\"\n\t\t\t\t\t}\n\t\t\t\t}`, token, metricsServiceName, namespace, serviceAccountName))\n\t\t\t_, err = utils.Run(cmd)\n\t\t\tExpect(err).NotTo(HaveOccurred(), \"Failed to create curl-metrics pod\")\n\n\t\t\tBy(\"waiting for the curl-metrics pod to complete.\")\n\t\t\tverifyCurlUp := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"pods\", \"curl-metrics\",\n\t\t\t\t\t\"-o\", \"jsonpath={.status.phase}\",\n\t\t\t\t\t\"-n\", namespace)\n\t\t\t\toutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(output).To(Equal(\"Succeeded\"), \"curl pod in wrong status\")\n\t\t\t}\n\t\t\tEventually(verifyCurlUp, 5*time.Minute).Should(Succeed())\n\n\t\t\tBy(\"getting the metrics by checking curl-metrics logs\")\n\t\t\tverifyMetricsAvailable := func(g Gomega) {\n\t\t\t\tmetricsOutput, err := getMetricsOutput()\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t\t\tg.Expect(metricsOutput).NotTo(BeEmpty())\n\t\t\t\tg.Expect(metricsOutput).To(ContainSubstring(\"< HTTP/1.1 200 OK\"))\n\t\t\t}\n\t\t\tEventually(verifyMetricsAvailable, 2*time.Minute).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should provisioned cert-manager\", func() {\n\t\t\tBy(\"validating that cert-manager has the certificate Secret\")\n\t\t\tverifyCertManager := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\", \"secrets\", \"webhook-server-cert\", \"-n\", namespace)\n\t\t\t\t_, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t\tEventually(verifyCertManager).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for validating webhooks\", func() {\n\t\t\tBy(\"checking CA injection for validating webhooks\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"validatingwebhookconfigurations.admissionregistration.k8s.io\",\n\t\t\t\t\t\"project-v4-with-plugins-validating-webhook-configuration\",\n\t\t\t\t\t\"-o\", \"go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\tIt(\"should have CA injection for Wordpress conversion webhook\", func() {\n\t\t\tBy(\"checking CA injection for Wordpress conversion webhook\")\n\t\t\tverifyCAInjection := func(g Gomega) {\n\t\t\t\tcmd := exec.Command(\"kubectl\", \"get\",\n\t\t\t\t\t\"customresourcedefinitions.apiextensions.k8s.io\",\n\t\t\t\t\t\"wordpresses.example.com.testproject.org\",\n\t\t\t\t\t\"-o\", \"go-template={{ .spec.conversion.webhook.clientConfig.caBundle }}\")\n\t\t\t\tvwhOutput, err := utils.Run(cmd)\n\t\t\t\tg.Expect(err).NotTo(HaveOccurred())\n\t\t\t\tg.Expect(len(vwhOutput)).To(BeNumerically(\">\", 10))\n\t\t\t}\n\t\t\tEventually(verifyCAInjection).Should(Succeed())\n\t\t})\n\n\t\t// +kubebuilder:scaffold:e2e-webhooks-checks\n\n\t\t// TODO: Customize the e2e test suite with scenarios specific to your project.\n\t\t// Consider applying sample/CR(s) and check their status and/or verifying\n\t\t// the reconciliation by using the metrics, i.e.:\n\t\t// metricsOutput, err := getMetricsOutput()\n\t\t// Expect(err).NotTo(HaveOccurred(), \"Failed to retrieve logs from curl pod\")\n\t\t// Expect(metricsOutput).To(ContainSubstring(\n\t\t//    fmt.Sprintf(`controller_runtime_reconcile_total{controller=\"%s\",result=\"success\"} 1`,\n\t\t//    strings.ToLower(<Kind>),\n\t\t// ))\n\t})\n})\n\n// serviceAccountToken returns a token for the specified service account in the given namespace.\n// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request\n// and parsing the resulting token from the API response.\nfunc serviceAccountToken() (string, error) {\n\tconst tokenRequestRawString = `{\n\t\t\"apiVersion\": \"authentication.k8s.io/v1\",\n\t\t\"kind\": \"TokenRequest\"\n\t}`\n\n\t// Temporary file to store the token request\n\tsecretName := fmt.Sprintf(\"%s-token-request\", serviceAccountName)\n\ttokenRequestFile := filepath.Join(\"/tmp\", secretName)\n\terr := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar out string\n\tverifyTokenCreation := func(g Gomega) {\n\t\t// Execute kubectl command to create the token\n\t\tcmd := exec.Command(\"kubectl\", \"create\", \"--raw\", fmt.Sprintf(\n\t\t\t\"/api/v1/namespaces/%s/serviceaccounts/%s/token\",\n\t\t\tnamespace,\n\t\t\tserviceAccountName,\n\t\t), \"-f\", tokenRequestFile)\n\n\t\toutput, err := cmd.CombinedOutput()\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\t// Parse the JSON output to extract the token\n\t\tvar token tokenRequest\n\t\terr = json.Unmarshal(output, &token)\n\t\tg.Expect(err).NotTo(HaveOccurred())\n\n\t\tout = token.Status.Token\n\t}\n\tEventually(verifyTokenCreation).Should(Succeed())\n\n\treturn out, err\n}\n\n// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.\nfunc getMetricsOutput() (string, error) {\n\tBy(\"getting the curl-metrics logs\")\n\tcmd := exec.Command(\"kubectl\", \"logs\", \"curl-metrics\", \"-n\", namespace)\n\treturn utils.Run(cmd)\n}\n\n// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response,\n// containing only the token field that we need to extract.\ntype tokenRequest struct {\n\tStatus struct {\n\t\tToken string `json:\"token\"`\n\t} `json:\"status\"`\n}\n"
  },
  {
    "path": "testdata/project-v4-with-plugins/test/utils/utils.go",
    "content": "/*\nCopyright 2026 The Kubernetes authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t. \"github.com/onsi/ginkgo/v2\" // nolint:revive,staticcheck\n)\n\nconst (\n\tcertmanagerVersion = \"v1.20.0\"\n\tcertmanagerURLTmpl = \"https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml\"\n\n\tdefaultKindBinary  = \"kind\"\n\tdefaultKindCluster = \"kind\"\n)\n\nfunc warnError(err error) {\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"warning: %v\\n\", err)\n}\n\n// Run executes the provided command within this context\nfunc Run(cmd *exec.Cmd) (string, error) {\n\tdir, _ := GetProjectDir()\n\tcmd.Dir = dir\n\n\tif err := os.Chdir(cmd.Dir); err != nil {\n\t\t_, _ = fmt.Fprintf(GinkgoWriter, \"chdir dir: %q\\n\", err)\n\t}\n\n\tcmd.Env = append(os.Environ(), \"GO111MODULE=on\")\n\tcommand := strings.Join(cmd.Args, \" \")\n\t_, _ = fmt.Fprintf(GinkgoWriter, \"running: %q\\n\", command)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn string(output), fmt.Errorf(\"%q failed with error %q: %w\", command, string(output), err)\n\t}\n\n\treturn string(output), nil\n}\n\n// UninstallCertManager uninstalls the cert manager\nfunc UninstallCertManager() {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"delete\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\twarnError(err)\n\t}\n\n\t// Delete leftover leases in kube-system (not cleaned by default)\n\tkubeSystemLeases := []string{\n\t\t\"cert-manager-cainjector-leader-election\",\n\t\t\"cert-manager-controller\",\n\t}\n\tfor _, lease := range kubeSystemLeases {\n\t\tcmd = exec.Command(\"kubectl\", \"delete\", \"lease\", lease,\n\t\t\t\"-n\", \"kube-system\", \"--ignore-not-found\", \"--force\", \"--grace-period=0\")\n\t\tif _, err := Run(cmd); err != nil {\n\t\t\twarnError(err)\n\t\t}\n\t}\n}\n\n// InstallCertManager installs the cert manager bundle.\nfunc InstallCertManager() error {\n\turl := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)\n\tcmd := exec.Command(\"kubectl\", \"apply\", \"-f\", url)\n\tif _, err := Run(cmd); err != nil {\n\t\treturn err\n\t}\n\t// Wait for cert-manager-webhook to be ready, which can take time if cert-manager\n\t// was re-installed after uninstalling on a cluster.\n\tcmd = exec.Command(\"kubectl\", \"wait\", \"deployment.apps/cert-manager-webhook\",\n\t\t\"--for\", \"condition=Available\",\n\t\t\"--namespace\", \"cert-manager\",\n\t\t\"--timeout\", \"5m\",\n\t)\n\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed\n// by verifying the existence of key CRDs related to Cert Manager.\nfunc IsCertManagerCRDsInstalled() bool {\n\t// List of common Cert Manager CRDs\n\tcertManagerCRDs := []string{\n\t\t\"certificates.cert-manager.io\",\n\t\t\"issuers.cert-manager.io\",\n\t\t\"clusterissuers.cert-manager.io\",\n\t\t\"certificaterequests.cert-manager.io\",\n\t\t\"orders.acme.cert-manager.io\",\n\t\t\"challenges.acme.cert-manager.io\",\n\t}\n\n\t// Execute the kubectl command to get all CRDs\n\tcmd := exec.Command(\"kubectl\", \"get\", \"crds\")\n\toutput, err := Run(cmd)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check if any of the Cert Manager CRDs are present\n\tcrdList := GetNonEmptyLines(output)\n\tfor _, crd := range certManagerCRDs {\n\t\tfor _, line := range crdList {\n\t\t\tif strings.Contains(line, crd) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// LoadImageToKindClusterWithName loads a local docker image to the kind cluster\nfunc LoadImageToKindClusterWithName(name string) error {\n\tcluster := defaultKindCluster\n\tif v, ok := os.LookupEnv(\"KIND_CLUSTER\"); ok {\n\t\tcluster = v\n\t}\n\tkindOptions := []string{\"load\", \"docker-image\", name, \"--name\", cluster}\n\tkindBinary := defaultKindBinary\n\tif v, ok := os.LookupEnv(\"KIND\"); ok {\n\t\tkindBinary = v\n\t}\n\tcmd := exec.Command(kindBinary, kindOptions...)\n\t_, err := Run(cmd)\n\treturn err\n}\n\n// GetNonEmptyLines converts given command output string into individual objects\n// according to line breakers, and ignores the empty elements in it.\nfunc GetNonEmptyLines(output string) []string {\n\tvar res []string\n\telements := strings.SplitSeq(output, \"\\n\")\n\tfor element := range elements {\n\t\tif element != \"\" {\n\t\t\tres = append(res, element)\n\t\t}\n\t}\n\n\treturn res\n}\n\n// GetProjectDir will return the directory where the project is\nfunc GetProjectDir() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn wd, fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\twd = strings.ReplaceAll(wd, \"/test/e2e\", \"\")\n\treturn wd, nil\n}\n\n// UncommentCode searches for target in the file and remove the comment prefix\n// of the target content. The target content may span multiple lines.\nfunc UncommentCode(filename, target, prefix string) error {\n\t// false positive\n\t// nolint:gosec\n\tcontent, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %q: %w\", filename, err)\n\t}\n\tstrContent := string(content)\n\n\tidx := strings.Index(strContent, target)\n\tif idx < 0 {\n\t\treturn fmt.Errorf(\"unable to find the code %q to be uncommented\", target)\n\t}\n\n\tout := new(bytes.Buffer)\n\t_, err = out.Write(content[:idx])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\tscanner := bufio.NewScanner(bytes.NewBufferString(target))\n\tif !scanner.Scan() {\n\t\treturn nil\n\t}\n\tfor {\n\t\tif _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t\t// Avoid writing a newline in case the previous line was the last in target.\n\t\tif !scanner.Scan() {\n\t\t\tbreak\n\t\t}\n\t\tif _, err = out.WriteString(\"\\n\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t\t}\n\t}\n\n\tif _, err = out.Write(content[idx+len(target):]); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to output: %w\", err)\n\t}\n\n\t// false positive\n\t// nolint:gosec\n\tif err = os.WriteFile(filename, out.Bytes(), 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file %q: %w\", filename, err)\n\t}\n\n\treturn nil\n}\n"
  }
]